<br />
<b>Notice</b>:  curl_setopt_array(): CURLOPT_SSL_VERIFYHOST no longer accepts the value 1, value 2 will be used instead in <b>/www/wwwroot/tvbox.baoma888.eu.org/index.php</b> on line <b>601</b><br />
<br />
<b>Deprecated</b>:  Function curl_close() is deprecated since 8.5, as it has no effect since PHP 8.0 in <b>/www/wwwroot/tvbox.baoma888.eu.org/index.php</b> on line <b>612</b><br />


<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no, maximum-scale=1.0">
    <title>漂流留言本 · 48h自动消失</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            -webkit-tap-highlight-color: transparent;
        }

        body {
            background: linear-gradient(145deg, #e8f0fe 0%, #d9e4f5 100%);
            font-family: system-ui, -apple-system, 'Segoe UI', 'SF Pro Text', Roboto, Helvetica, sans-serif;
            padding: 16px 12px 40px;
            min-height: 100vh;
        }

        /* 主容器 */
        .guestbook {
            max-width: 550px;
            margin: 0 auto;
            background: rgba(255, 255, 255, 0.88);
            backdrop-filter: blur(8px);
            border-radius: 40px 28px 48px 28px;
            box-shadow: 0 20px 35px -10px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255,255,255,0.7);
            overflow: hidden;
        }

        /* 头部 */
        .header {
            background: #ffffffcc;
            backdrop-filter: blur(12px);
            padding: 20px 20px 14px;
            text-align: center;
            border-bottom: 1px solid rgba(0,0,0,0.05);
        }

        .header h1 {
            font-size: 1.7rem;
            font-weight: 700;
            background: linear-gradient(135deg, #1e3b5c, #2c5282);
            background-clip: text;
            -webkit-background-clip: text;
            color: transparent;
            display: inline-flex;
            align-items: center;
            gap: 8px;
        }

        .header h1::before {
            content: "⏳";
            font-size: 1.6rem;
            background: none;
            color: #2c5282;
        }

        .badge {
            display: flex;
            justify-content: center;
            gap: 12px;
            margin-top: 10px;
            flex-wrap: wrap;
        }

        .badge span {
            background: #eef2ff;
            padding: 4px 12px;
            border-radius: 60px;
            font-size: 0.7rem;
            font-weight: 500;
            color: #2c3e66;
        }

        /* 表单卡片 */
        .form-card {
            background: white;
            margin: 18px 18px 0 18px;
            padding: 16px 18px 18px;
            border-radius: 32px;
            box-shadow: 0 6px 14px rgba(0, 0, 0, 0.03), 0 0 0 1px rgba(0,0,0,0.02);
        }

        .input-group {
            margin-bottom: 14px;
        }

        .input-group label {
            display: block;
            font-size: 0.75rem;
            font-weight: 600;
            color: #2c3e50;
            margin-bottom: 6px;
            letter-spacing: 0.3px;
        }

        .input-group input, 
        .input-group textarea {
            width: 100%;
            border: 1.5px solid #e2edf7;
            border-radius: 28px;
            padding: 12px 18px;
            font-size: 0.95rem;
            font-family: inherit;
            background: #fefefe;
            transition: all 0.2s;
            outline: none;
            resize: none;
        }

        .input-group input:focus,
        .input-group textarea:focus {
            border-color: #3b82f6;
            box-shadow: 0 0 0 3px rgba(59,130,246,0.15);
            background: white;
        }

        .input-group textarea {
            min-height: 70px;
            border-radius: 24px;
        }

        .char-counter {
            text-align: right;
            font-size: 0.65rem;
            color: #7f8c8d;
            margin-top: 5px;
            font-weight: 500;
        }

        .limit-info {
            background: #f8fafc;
            border-radius: 20px;
            padding: 8px 12px;
            font-size: 0.7rem;
            color: #4b5563;
            display: flex;
            justify-content: space-between;
            margin-bottom: 14px;
            flex-wrap: wrap;
            gap: 8px;
        }

        .limit-info span:first-child {
            font-weight: 500;
        }

        .remain-count {
            background: #e2e8f0;
            padding: 2px 12px;
            border-radius: 30px;
            font-weight: 600;
            color: #1e293b;
        }

        .btn-post {
            background: linear-gradient(98deg, #1f4870, #2b5a84);
            border: none;
            width: 100%;
            padding: 14px 0;
            font-size: 1rem;
            font-weight: 600;
            color: white;
            border-radius: 60px;
            cursor: pointer;
            transition: all 0.2s;
            box-shadow: 0 5px 12px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }

        .btn-post:active {
            transform: scale(0.97);
            background: linear-gradient(98deg, #153a58, #1f4870);
        }

        .btn-post.disabled {
            opacity: 0.6;
            transform: none;
            pointer-events: none;
        }

        /* 留言列表区域 */
        .messages-container {
            padding: 20px 18px 24px;
        }

        .section-header {
            display: flex;
            align-items: baseline;
            justify-content: space-between;
            margin-bottom: 16px;
            flex-wrap: wrap;
            gap: 10px;
        }

        .section-header h2 {
            font-size: 1.2rem;
            font-weight: 600;
            color: #1e293b;
        }

        .auto-badge {
            background: #e6f7ec;
            padding: 4px 12px;
            border-radius: 40px;
            font-size: 0.65rem;
            font-weight: 500;
            color: #2b6e3c;
        }

        .message-list {
            display: flex;
            flex-direction: column;
            gap: 14px;
        }

        .message-card {
            background: white;
            border-radius: 28px;
            padding: 14px 18px;
            transition: all 0.2s;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02), 0 0 0 1px #eef2ff;
        }

        .message-header {
            display: flex;
            align-items: baseline;
            justify-content: space-between;
            margin-bottom: 10px;
            flex-wrap: wrap;
            gap: 6px;
        }

        .author {
            font-weight: 700;
            font-size: 0.9rem;
            background: #f1f5f9;
            padding: 3px 12px;
            border-radius: 40px;
            color: #0c4a6e;
            max-width: 65%;
            word-break: break-word;
        }

        .time-left {
            font-size: 0.6rem;
            background: #fff3e0;
            padding: 3px 10px;
            border-radius: 30px;
            color: #b45309;
            font-weight: 500;
        }

        .message-content {
            font-size: 0.9rem;
            line-height: 1.4;
            color: #1e293b;
            word-break: break-word;
            white-space: pre-wrap;
            padding-left: 8px;
            border-left: 3px solid #a0c4ff;
            margin-top: 4px;
        }

        .empty-state {
            text-align: center;
            padding: 48px 20px;
            background: #f9fbfe;
            border-radius: 40px;
            color: #6c7a91;
            font-size: 0.85rem;
        }

        .footer-note {
            text-align: center;
            font-size: 0.6rem;
            color: #5a6e8a;
            padding: 12px 20px 20px;
            border-top: 1px solid rgba(0,0,0,0.05);
        }

        /* 动画 */
        @keyframes fadeSlide {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .message-card {
            animation: fadeSlide 0.2s ease;
        }

        /* Toast 提示 */
        .toast-msg {
            position: fixed;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            background: #1e293bb3;
            backdrop-filter: blur(12px);
            color: white;
            padding: 10px 20px;
            border-radius: 60px;
            font-size: 0.8rem;
            font-weight: 500;
            z-index: 2000;
            white-space: nowrap;
            max-width: 85%;
            white-space: normal;
            text-align: center;
            pointer-events: none;
            transition: opacity 0.2s;
        }

        @media (max-width: 480px) {
            body {
                padding: 12px 8px 30px;
            }
            .form-card {
                margin: 14px 14px 0;
                padding: 14px;
            }
            .btn-post {
                padding: 12px 0;
            }
        }
    </style>
</head>
<body>

<div class="guestbook">
    <div class="header">
        <h1>漂流留言本</h1>
        <div class="badge">
            <span>⏱️ 48小时自动消失</span>
            <span>📝 最多20字</span>
            <span>🌐 1IP/日 限2条</span>
        </div>
    </div>

    <div class="form-card">
        <div class="limit-info" id="limitInfo">
            <span>📮 今日剩余留言次数</span>
            <span class="remain-count" id="remainCount">加载中</span>
        </div>
        <div class="input-group">
            <label>👤 昵称 (选填)</label>
            <input type="text" id="nicknameInput" placeholder="路人甲 · 匿名也行" maxlength="12" autocomplete="off">
        </div>
        <div class="input-group">
            <label>💬 留言 · 最多20个字</label>
            <textarea id="messageInput" placeholder="写点什么吧，48小时后消失..." maxlength="20" rows="2"></textarea>
            <div class="char-counter" id="charCounter">0 / 20</div>
        </div>
        <button class="btn-post" id="postBtn">✨ 留下足迹</button>
    </div>

    <div class="messages-container">
        <div class="section-header">
            <h2>🌊 漂流中的留言</h2>
            <div class="auto-badge">⏳ 剩余时间倒计时</div>
        </div>
        <div id="messageList" class="message-list">
            <div class="empty-state">🍃 暂无漂流留言<br>写一条，48小时后它会随风而逝</div>
        </div>
    </div>
    <div class="footer-note">
        💡 每条留言保留48小时 · 自动清理 · 每个IP每天最多2条
    </div>
</div>

<script>
    (function(){
        // ---------- 存储KEY ----------
        const STORAGE_KEY = 'drift_guestbook_msgs';
        const IP_LIMIT_KEY = 'ip_post_record';

        // ---------- 获取客户端标识 (基于IP+localStorage模拟，用访客指纹：localStorage + 部分浏览器特征)
        // 因为纯前端无法获取真实IP，但为了满足“1个IP 24h 最多2次”，使用 localStorage 存储设备标识 + 时间戳限制。
        // 所有用户公用同一个localStorage，但不同设备/浏览器会独立计数。为了更接近IP限制概念，使用localStorage存储一个“设备指纹”，实际等同于“当前浏览器”限制。
        // 可结合用户代理+屏幕分辨率+时区做简单指纹，确保同一个人在同一手机浏览器上受限。完全模拟IP限制体验。
        function getDeviceId() {
            let device = localStorage.getItem('device_fingerprint');
            if(!device) {
                const ua = navigator.userAgent;
                const lang = navigator.language;
                const screenRes = `${screen.width}x${screen.height}`;
                const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
                const fingerprint = btoa(unescape(encodeURIComponent(ua + lang + screenRes + timezone))).substring(0, 32);
                device = fingerprint;
                localStorage.setItem('device_fingerprint', device);
            }
            return device;
        }

        // 获取IP限制记录 (每日发布次数)
        function getIPLimitRecord() {
            const deviceId = getDeviceId();
            const stored = localStorage.getItem(IP_LIMIT_KEY);
            let records = {};
            if(stored) {
                try {
                    records = JSON.parse(stored);
                } catch(e) {}
            }
            const today = new Date().toDateString();
            if(!records[deviceId]) {
                records[deviceId] = { date: today, count: 0 };
            } else if(records[deviceId].date !== today) {
                // 新的一天重置
                records[deviceId] = { date: today, count: 0 };
            }
            return { deviceId, record: records[deviceId], fullRecords: records };
        }

        // 检查是否可以发布 (返回剩余次数)
        function canPostRemain() {
            const { record } = getIPLimitRecord();
            const used = record.count;
            const remain = Math.max(0, 2 - used);
            return remain;
        }

        // 增加发布计数
        function incPostCount() {
            const { deviceId, record, fullRecords } = getIPLimitRecord();
            if(record.count < 2) {
                record.count += 1;
                fullRecords[deviceId] = record;
                localStorage.setItem(IP_LIMIT_KEY, JSON.stringify(fullRecords));
                return true;
            }
            return false;
        }

        // 更新剩余次数UI
        function updateRemainUI() {
            const remainSpan = document.getElementById('remainCount');
            if(remainSpan) {
                const remain = canPostRemain();
                remainSpan.innerText = `${remain} / 2`;
                if(remain === 0) {
                    remainSpan.style.backgroundColor = '#fee2e2';
                    remainSpan.style.color = '#b91c1c';
                } else {
                    remainSpan.style.backgroundColor = '#e2e8f0';
                    remainSpan.style.color = '#1e293b';
                }
            }
        }

        // ---------- 留言数据模型 ----------
        let messages = [];   // { id, nickname, content, timestamp(ms), expireAt(ms) }

        // 加载留言 (自动清理48小时过期的)
        function loadAndCleanMessages() {
            const stored = localStorage.getItem(STORAGE_KEY);
            let raw = [];
            if(stored) {
                try {
                    raw = JSON.parse(stored);
                    if(!Array.isArray(raw)) raw = [];
                } catch(e) { raw = []; }
            }
            const now = Date.now();
            const EXPIRATION_MS = 48 * 60 * 60 * 1000; // 48小时
            let cleaned = raw.filter(msg => {
                if(!msg || !msg.timestamp) return false;
                // 兼容老数据：没有expireAt则根据timestamp计算
                const expire = msg.expireAt || (msg.timestamp + EXPIRATION_MS);
                return expire > now;
            });
            // 规范化每条消息，并补充剩余时间字段（用于渲染）
            messages = cleaned.map(msg => ({
                id: msg.id || Date.now() + Math.random(),
                nickname: (msg.nickname && msg.nickname.trim()) ? msg.nickname.trim().substring(0,12) : '匿名',
                content: msg.content ? msg.content.trim().substring(0,20) : '',
                timestamp: msg.timestamp,
                expireAt: msg.expireAt || (msg.timestamp + EXPIRATION_MS)
            })).filter(m => m.content.length > 0);
            // 按发布时间倒序
            messages.sort((a,b) => b.timestamp - a.timestamp);
            // 保存清理后的干净数据
            saveMessages();
            return messages;
        }

        // 保存留言
        function saveMessages() {
            // 限制最多保留50条避免占用太多，不过自动过期会自动清除
            if(messages.length > 100) messages = messages.slice(0,100);
            localStorage.setItem(STORAGE_KEY, JSON.stringify(messages));
        }

        // 格式化剩余时间（动态）
        function getRemainingTimeText(expireAt) {
            const now = Date.now();
            const diff = expireAt - now;
            if(diff <= 0) return '即将消失';
            const hours = Math.floor(diff / (1000 * 60 * 60));
            const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
            if(hours >= 24) {
                const days = Math.floor(hours / 24);
                return `${days}天${hours % 24}小时后消失`;
            } else if(hours > 0) {
                return `${hours}小时${minutes}分后消失`;
            } else if(minutes > 0) {
                return `${minutes}分钟后消失`;
            } else {
                return '即将消失';
            }
        }

        // 渲染留言列表（带倒计时动态刷新）
        let timerInterval = null;
        function renderMessages() {
            const container = document.getElementById('messageList');
            if(!container) return;
            if(!messages.length) {
                container.innerHTML = `<div class="empty-state">🍃 暂无漂流留言<br>写一条，48小时后它会随风而逝</div>`;
                return;
            }
            const fragment = document.createDocumentFragment();
            messages.forEach(msg => {
                const card = document.createElement('div');
                card.className = 'message-card';
                const safeNick = escapeHtml(msg.nickname) || '匿名';
                const safeContent = escapeHtml(msg.content);
                const remainText = getRemainingTimeText(msg.expireAt);
                card.innerHTML = `
                    <div class="message-header">
                        <span class="author">${safeNick}</span>
                        <span class="time-left">⏳ ${escapeHtml(remainText)}</span>
                    </div>
                    <div class="message-content">${safeContent}</div>
                `;
                fragment.appendChild(card);
            });
            container.innerHTML = '';
            container.appendChild(fragment);
        }

        // 实时更新倒计时（每秒刷新剩余时间显示）
        function startLiveTimer() {
            if(timerInterval) clearInterval(timerInterval);
            timerInterval = setInterval(() => {
                // 先清理过期留言（重新加载一遍）
                const beforeCount = messages.length;
                loadAndCleanMessages();  // 会全局清理过期并重排
                if(beforeCount !== messages.length) {
                    // 留言数量变化，重新渲染
                    renderMessages();
                    updateRemainUI();
                } else {
                    // 仅更新时间文本，避免重绘所有卡片的开销，但为了简洁且保证显示正确，重新渲染
                    // 因为剩余时间改变只需更新 .time-left 文本，但性能无碍，小数据直接重绘即可
                    renderMessages();
                }
            }, 1000);
        }

        // 新增留言 (核心)
        function addNewMessage(nickname, content) {
            // 1. 字数限制
            if(!content || content.trim() === '') {
                showToast('留言内容不能为空', 'error');
                return false;
            }
            const cleanContent = content.trim();
            if(cleanContent.length > 20) {
                showToast('最多只能写20个字～', 'error');
                return false;
            }
            // 2. IP/设备 每日限制
            const remain = canPostRemain();
            if(remain <= 0) {
                showToast('今日留言次数已达上限 (2条/天)，明天再来吧～', 'error');
                return false;
            }
            // 3. 昵称处理
            let finalNick = nickname.trim();
            if(finalNick === '') finalNick = '匿名旅人';
            if(finalNick.length > 12) finalNick = finalNick.substring(0,12);
            
            // 4. 增加计数
            const successInc = incPostCount();
            if(!successInc) {
                showToast('发布失败，请检查今日次数', 'error');
                return false;
            }
            // 5. 创建留言
            const now = Date.now();
            const expireAt = now + 48 * 60 * 60 * 1000;
            const newMsg = {
                id: now + '_' + Math.random().toString(36).substring(2,8),
                nickname: finalNick,
                content: cleanContent,
                timestamp: now,
                expireAt: expireAt
            };
            messages.unshift(newMsg);
            saveMessages();
            renderMessages();
            updateRemainUI();
            showToast('✨ 留言已漂流 · 48小时后消失', 'success');
            return true;
        }

        // 显示浮动提示
        function showToast(msg, type = 'info') {
            let existingToast = document.querySelector('.toast-msg');
            if(existingToast) existingToast.remove();
            const toast = document.createElement('div');
            toast.className = 'toast-msg';
            toast.innerText = msg;
            toast.style.background = type === 'error' ? '#b91c1cd9' : '#1e293bd9';
            toast.style.backdropFilter = 'blur(12px)';
            document.body.appendChild(toast);
            setTimeout(() => {
                toast.style.opacity = '0';
                setTimeout(() => { if(toast.parentNode) toast.parentNode.removeChild(toast); }, 300);
            }, 2200);
        }

        // 防XSS
        function escapeHtml(str) {
            if(!str) return '';
            return str.replace(/[&<>]/g, function(m) {
                if(m === '&') return '&amp;';
                if(m === '<') return '&lt;';
                if(m === '>') return '&gt;';
                return m;
            });
        }

        // 字数统计
        function bindCharCounter() {
            const textarea = document.getElementById('messageInput');
            const counterSpan = document.getElementById('charCounter');
            if(textarea && counterSpan) {
                const update = () => {
                    const len = textarea.value.length;
                    counterSpan.innerText = `${len} / 20`;
                    if(len > 20) {
                        counterSpan.style.color = '#e53e3e';
                    } else {
                        counterSpan.style.color = '#7f8c8d';
                    }
                };
                textarea.addEventListener('input', update);
                update();
            }
        }

        // 发布处理
        let isPosting = false;
        function handlePost() {
            if(isPosting) return;
            const remain = canPostRemain();
            if(remain <= 0) {
                showToast('今日留言次数已用完 (2次/天)', 'error');
                return;
            }
            const nickname = document.getElementById('nicknameInput')?.value || '';
            const content = document.getElementById('messageInput')?.value || '';
            if(content.trim().length === 0) {
                showToast('写点什么吧～', 'error');
                return;
            }
            if(content.trim().length > 20) {
                showToast('留言不能超过20个字', 'error');
                return;
            }
            isPosting = true;
            const success = addNewMessage(nickname, content);
            if(success) {
                // 清空输入框但保留昵称记忆
                const msgInput = document.getElementById('messageInput');
                if(msgInput) msgInput.value = '';
                const nickInput = document.getElementById('nicknameInput');
                if(nickInput && nickInput.value.trim() !== '') {
                    // 保留昵称，不自动清空
                }
                if(msgInput) msgInput.focus();
                bindCharCounter(); // 重置计数器
            }
            setTimeout(() => { isPosting = false; }, 500);
        }

        // 同步多窗口存储变化
        function watchStorageChange() {
            window.addEventListener('storage', (event) => {
                if(event.key === STORAGE_KEY || event.key === IP_LIMIT_KEY) {
                    // 重新加载留言 & 刷新UI
                    loadAndCleanMessages();
                    renderMessages();
                    updateRemainUI();
                }
            });
        }

        // 初始化和清理定时器
        function init() {
            loadAndCleanMessages();
            renderMessages();
            updateRemainUI();
            bindCharCounter();
            startLiveTimer();
            watchStorageChange();
            const postBtn = document.getElementById('postBtn');
            if(postBtn) postBtn.addEventListener('click', handlePost);
            // 移动端软键盘优化：回车快捷（仅限留言框按Ctrl+Enter或直接Enter? 为避免误触只做发送按钮）
            const textareaEl = document.getElementById('messageInput');
            if(textareaEl) {
                textareaEl.addEventListener('keydown', (e) => {
                    if(e.key === 'Enter' && !e.shiftKey) {
                        e.preventDefault();
                        handlePost();
                    }
                });
            }
            // 定期清理过期（双重保险）
            setInterval(() => {
                const changed = loadAndCleanMessages();
                if(changed) renderMessages();
            }, 60000);
        }
        
        init();
    })();
</script>
</body>
</html>