<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, shrink-to-fit=no">
    <meta name="theme-color" content="#FFF0F5">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <title>Mori-Diary</title>
    <link rel="icon" type="image/png" href="/assets/img/icon.png">
    <link rel="apple-touch-icon" href="/assets/img/icon.png">
    <meta name="application-name" content="Mori-Diary">
    <!-- 引入圆润可爱的字体 -->
    <link href="https://fonts.googleapis.com/css2?family=Varela+Round&display=swap" rel="stylesheet">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/1.5.0/ical.min.js"></script>
    <style>
        /* --- 🎨 Chiikawa 恋爱调色盘 --- */
        :root {
            --pink: #FFB7C5;    /* 吉伊粉 */
            --blue: #89CFF0;    /* 小八蓝 */
            --yellow: #FFF59D;  /* 乌萨奇黄 */
            --dark: #5D4037;    /* 巧克力色文字 */
            --white: #FFFFFF;
            --bg-gradient: linear-gradient(135deg, #FFFBF0 0%, #FFF0F5 100%);
            --glass: rgba(255, 255, 255, 0.92);
            --shadow-soft: 0 10px 30px rgba(255, 183, 197, 0.25);
            --shadow-btn: 0 4px 0 rgba(0,0,0,0.08);
            --danger: #FF6B6B;
            --stroke: #5D4037;
            /* 底部导航的固定高度（不含安全区） — 调高约 2x */
            --nav-height: calc(56px * 4 / 3);
            --nav-gap: 8px; /* 内容与底栏之间的额外留白（可调）*/
        }

        /* 发射台视图：希望内容更贴近（甚至轻微压住）底栏 */
        .app-container.home-tight { --nav-gap: -10px; }

        * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
        html { background: var(--bg-gradient); }

        body {
            margin: 0;
            padding: 0;
            background: var(--bg-gradient);
            font-family: 'Varela Round', system-ui, sans-serif;
            color: var(--dark);
            height: 100dvh; /* iOS 以动态可视高度，全屏适配 */
            display: flex;
            flex-direction: column;
            align-items: center;
            overflow: hidden; /* 禁止页面层滚动 */
            overscroll-behavior: none;
        }

        /* 背景装饰 - 漂浮的SVG星星 */
        .bg-decoration {
            position: fixed;
            top: 0; left: 0; width: 100%; height: 100%;
            pointer-events: none;
            z-index: -1;
            opacity: 0.6;
            background-image: 
                radial-gradient(#FFB7C5 15%, transparent 16%),
                radial-gradient(#89CFF0 15%, transparent 16%);
            background-size: 60px 60px;
            background-position: 0 0, 30px 30px;
        }

        /* 事件详情弹层（居中弹窗 + 丝滑动画） */
        #evtModal { position: fixed; inset: 0; background: rgba(0,0,0,0.25); opacity: 0; pointer-events: none; transition: opacity .18s ease; z-index: 100000; }
        #evtModal.show { opacity: 1; pointer-events: auto; }
        /* 事件弹窗整体下移约两行高度，避免被顶端遮挡 */
        #evtSheet { position: fixed; left: 50%; top: calc(50% + 28px); transform: translate(-50%,-46%) scale(.98); width: calc(100% - 40px); max-width: 460px; background: var(--glass); border-radius: 16px; box-shadow: var(--shadow-soft); transition: transform .22s ease, opacity .22s ease; padding: 16px 18px; border: 2px solid #00000010; backdrop-filter: blur(10px); opacity: 1; }
        #evtModal.show #evtSheet { transform: translate(-50%,-50%) scale(1); }
        #evtTitle { font-weight: 800; font-size: 16px; line-height: 1.4; color: var(--stroke); word-break: break-word; }
        #evtMeta { margin-top: 8px; font-size: 13px; opacity: .85; }
        #evtActions { margin-top: 14px; display: flex; justify-content: flex-end; gap: 8px; }

        /* 天气胶囊 - Kawaii 软萌风 */
        .weather-pill {
            /* 从当前基础上再向上移动约 4 行（-4em => 比原始位置高约 2 行） */
            position: fixed; top: calc(env(safe-area-inset-top) + 10px - 2em); left: 10px; z-index: 1200;
            font-family: inherit; -webkit-font-smoothing: antialiased; font-smooth: always;
        }
        .weather-pill .pill {
            display: flex; align-items: center; gap: 10px;
            padding: 8px 12px; background: rgba(255,255,255,0.95);
            border: 2px solid #FFB6C1; border-radius: 999px; backdrop-filter: blur(6px);
            box-shadow: 3px 3px 0 rgba(255,182,193,0.6);
            transition: transform 0.1s ease, box-shadow 0.1s ease;
        }
        .weather-pill .pill:hover { transform: translateY(-1px); box-shadow: 4px 4px 0 rgba(255,182,193,0.4); }
        .weather-pill .info { display: flex; align-items: baseline; gap: 8px; line-height: 1; }
        .weather-pill .sheng { font-size: 10px; font-weight: 800; color: #9AA0A6; letter-spacing: .06em; text-transform: uppercase; }
        .weather-pill .city { font-size: 13px; font-weight: 900; color: #444; }
        .weather-pill .sep { width: 1px; height: 12px; background: #D0D5D9; margin: 0 4px; align-self: center; }
        .weather-pill .cond { font-size: 12px; font-weight: 800; color: #87CEEB; background: rgba(135,206,235,0.15); padding: 2px 6px; border-radius: 6px; }
        .weather-pill .temp { font-size: 18px; font-weight: 900; color: #FFB6C1; letter-spacing: -0.02em; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
        .weather-pill .iconwrap { width: 32px; height: 32px; border-radius: 999px; background: rgba(135,206,235,0.16); border: 1px solid #FFF; box-shadow: 0 1px 2px rgba(0,0,0,0.04); display:flex; align-items:center; justify-content:center; margin: -2px 0; }
        .weather-pill img { width: 24px; height: 24px; object-fit: contain; display: none; }

        /* 下拉刷新指示器（猫爪风格） */
        .pull-indicator {
            position: fixed;
            top: calc(env(safe-area-inset-top) + 6px); left: 50%; transform: translateX(-50%) translateY(-40px);
            z-index: 1201;
            display: flex; align-items: center; gap: 10px;
            padding: 6px 12px; border-radius: 999px;
            background: rgba(255,255,255,0.95);
            border: 2px solid #FFB6C1; box-shadow: 3px 3px 0 rgba(255,182,193,0.4);
            opacity: 0; pointer-events: none; transition: transform .15s ease, opacity .15s ease;
        }
        .pull-indicator.show { opacity: 1; transform: translateX(-50%) translateY(0); }
        .paw-wrap { width: 26px; height: 26px; border-radius: 50%; background: #FFF0F5; border: 1px solid #FFE4ED; display:flex; align-items:center; justify-content:center; }
        .paw-svg { width: 18px; height: 18px; display:block; }
        .pull-text { font-size: 12px; font-weight: 800; color: #5D4037; }
        .pull-line { width: 16px; height: 2px; background: #FFDDE7; border-radius: 2px; position: relative; overflow: hidden; }
        .pull-line::after { content:""; position:absolute; left:-20px; top:0; width: 20px; height:100%; background:#FFB6C1; border-radius: 2px; animation: slideLine 1s linear infinite; }
        @keyframes slideLine { 0%{left:-20px;} 100%{left:16px;} }
        .pull-rotate { animation: pawSpin 0.9s linear infinite; }
        @keyframes pawSpin { from { transform: rotate(0deg);} to { transform: rotate(360deg);} }

        /* 发射台状态卡片 */
        .status-card {
            background: #FFF;
            border: 2px solid #F4F4F4;
            border-radius: 20px;
            padding: 12px 14px;
            box-shadow: var(--shadow-btn);
        }
        .status-row { display: flex; align-items: center; gap: 8px; font-weight: 800; line-height: 1; white-space: nowrap; overflow: hidden; }
        .status-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; border: 2px solid #FFF; box-shadow: 0 1px 2px rgba(0,0,0,0.08); }
        .status-idle { background: #22c55e; }
        .status-busy { background: #f59e0b; }
        .status-sleeping { background: #9ca3af; }
        .status-dnd { background: #ef4444; position: relative; }
        .status-dnd::after { content: '✕'; position: absolute; inset: -6px -6px -10px -10px; color: #ef4444; font-weight: 900; font-size: 12px; transform: translate(10px, -4px); }
        .status-text, .status-weather { font-size: 0.95rem; color: #555; font-weight: 800; }
        .status-text { flex: 0 0 auto; }
        .status-sep { width: 1px; height: 14px; background: #E5E7EB; border-radius: 1px; }
        .status-sep { flex: 0 0 auto; }
        .status-weather { flex: 1 1 auto; min-width: 0; overflow: visible; display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
        .sw-prefix { flex: 0 0 auto; }
        .sw-window { display: inline-block; flex: 0 0 auto; width: 16ch; overflow: hidden; vertical-align: bottom; }
        .sw-window.marquee { text-overflow: clip; }

        /* 主容器 */
        .app-container {
            position: fixed; /* 固定容器，使滚动条在容器内 */
            top: calc(4px + env(safe-area-inset-top));
            left: 16px;
            right: 16px;
            margin: 0 auto;
            bottom: var(--nav-height); /* 初始占位，运行时会根据实际 nav 高度调整 */
            width: auto;
            max-width: 420px; /* 仅适配手机宽度 */
            background: var(--glass);
            backdrop-filter: blur(12px);
            border-radius: 36px;
            padding: 24px;
            box-shadow: var(--shadow-soft);
            border: 3px solid #FFF;
            overflow-y: auto; /* 默认在白色区块内滚动 */
            -webkit-overflow-scrolling: touch;
            overscroll-behavior: contain;
        }
        /* 日历模式下禁止容器滚动，仅允许内部时间轴滚动 */
        .app-container.no-scroll { overflow: hidden; }

        /* --- 视图切换逻辑 --- */
        .view-section {
            display: none;
            animation: fadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
        }
        .view-section.active {
            display: flex;
            flex-direction: column;
            gap: 24px;
            /* ⚡️ 修复：内容区底部额外缓冲 */
            padding-bottom: 20px;
        }

        /* --- 底部导航栏 (Bar) --- */
        .nav-bar {
            position: fixed;
            left: 0;
            right: 0;
            bottom: 0; /* 紧贴底部 */
            background: rgba(255, 255, 255, 0.95);
            backdrop-filter: blur(20px);
            border-radius: 24px 24px 0 0;
            /* 全屏贴底：不再为安全区保留额外留白 */
            padding-bottom: 0;
            box-shadow: 0 -6px 24px rgba(0,0,0,0.06);
            border-top: 2px solid #FFF;
            z-index: 99999; /* 提高层级，确保位于任何浮层之上 */
            pointer-events: auto;
        }
        .nav-inner {
            height: var(--nav-height); /* 可视高度，内容在此高度内居中 */
            display: flex;
            align-items: center;
            justify-content: space-around;
            padding: 10px 16px;
            max-width: 420px;
            width: 100%;
            margin: 0 auto;
        }
        .nav-item {
            flex: 1;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 3px;
            color: #AAA;
            font-size: 0.75rem;
            cursor: pointer;
            transition: 0.3s;
            padding: 6px 8px;
            border-radius: 16px;
            position: relative; /* 允许高亮遮罩定位 */
        }
        .nav-item svg { width: 22px; height: 22px; fill: currentColor; transition: 0.3s; }
        .nav-item.active { color: var(--dark); }
        .nav-item.active::before {
            content: "";
            position: absolute;
            left: 4px; right: 4px; top: 0; bottom: 0;
            background: #FFF9C4;
            border-radius: 16px;
            transform: translateY(3px); /* 让黄色遮罩整体更低一些 */
            z-index: -1;
        }
        .nav-item.active svg { transform: translateY(-2px); fill: var(--dark); }

        /* --- 📅 日历视图样式（简化版，适配容器） --- */
        .calendar-header { padding: 12px 8px 6px 8px; background: rgba(255,255,255,0.7); border-radius: 16px; border: 1px solid #F3F4F6; }
        .cal-head-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
        .cal-month { font-weight: 900; font-size: 1.05rem; }
        .cal-days-3 { display: flex; padding-left: 40px; }
        .cal-day-h { flex: 1; text-align: center; }
        .cal-day-name { font-size: 0.75rem; color: #999; }
        .cal-day-num { display:inline-block; width:28px; height:28px; line-height:28px; border-radius:50%; font-weight: 900; }
        .cal-day-h.active .cal-day-name { color: var(--blue); font-weight: 800; }
        .cal-day-h.active .cal-day-num { background: var(--blue); color: #fff; box-shadow: 0 4px 12px rgba(137,207,240,.35); }

        .calendar-body { position: relative; overflow: hidden; border-radius: 16px; border: 1px solid #F3F4F6; background: rgba(255,255,255,0.7); flex: 1; display: flex; }
        .cal-scroll { flex: 1; overflow: auto; position: relative; max-height: none; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; touch-action: pan-y; }
        .cal-grid { display: flex; position: relative; min-height: 1440px; }
        .cal-timebar { width: 40px; flex-shrink: 0; border-right: 1px solid #eee; background: rgba(255,255,255,0.4); }
        .cal-time { height: 60px; font-size: 10px; color: #999; text-align: center; position: relative; top: -8px; }
        .cal-cols { flex: 1; display: flex; position: relative; }
        .cal-lines { position:absolute; inset:0; pointer-events:none; }
        .cal-line { height: 60px; border-bottom: 1px solid #F1F5F9; }
        .cal-col { flex: 1; position: relative; border-right: 1px dashed rgba(0,0,0,0.035); }
        .evt { position: absolute; left: 6%; width: 88%; border-radius: 12px; padding: 6px 8px; font-size: 12px; border-left: 4px solid rgba(0,0,0,0.12); background: #FFF; box-shadow: 0 2px 8px rgba(0,0,0,0.05); overflow: hidden; }
        .evt .t { font-weight: 900; margin-bottom: 2px; }
        .evt .tm { font-size: 11px; color: #666; }
        .evt-pink { background: #FFEBEE; border-left-color: var(--pink); }
        .evt-blue { background: #E1F5FE; border-left-color: var(--blue); }
        .evt-yellow { background: #FFFDE7; border-left-color: var(--yellow); }
        .cal-now { position:absolute; left:0; right:0; border-top:2px solid var(--blue); display:flex; align-items:center; z-index:2; }
        .cal-now::before { content:""; width:10px; height:10px; border-radius:50%; background: var(--blue); margin-left:-5px; box-shadow: 0 0 0 2px rgba(137,207,240,.35); }

        /* 悬浮刷新按钮与更新时间小字 */
        .fab-refresh {
            position: fixed;
            right: 20px;
            top: calc(env(safe-area-inset-top) + 12px);
            width: 52px; height: 52px;
            border-radius: 50%;
            background: var(--pink);
            color: #fff;
            display: flex; align-items: center; justify-content: center;
            font-size: 22px; font-weight: 900;
            box-shadow: 0 6px 20px rgba(255, 183, 197, 0.5);
            z-index: 2500;
            border: none; cursor: pointer;
            transition: transform .12s ease;
        }
        .fab-refresh:active { transform: scale(0.94) rotate(20deg); }
        /* 更新小字已整合进顶部提示，不再单独显示 */

        /* --- 通用组件 --- */
        .header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 5px; }
        .char-group { display: flex; gap: 10px; }
        .char-icon {
            width: 50px; height: 50px; border-radius: 50%;
            border: 2px solid var(--white);
            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
            transition: transform 0.2s; cursor: pointer;
            background: white; padding: 5px;
        }
        .char-icon:hover { transform: translateY(-5px) rotate(10deg); }
        .license-badge {
            display: flex; flex-direction: column; align-items: center;
            background: #FFF; border: 2px solid #E0E0E0; border-radius: 12px;
            padding: 4px 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.05);
            transform: rotate(2deg);
        }
        .license-header { background: var(--blue); color: white; font-size: 0.6rem; padding: 2px 6px; border-radius: 4px; width: 100%; text-align: center; margin-bottom: 2px; }
        .license-body { font-size: 0.7rem; font-weight: bold; color: var(--dark); }

        /* --- 信号页样式 --- */
        .section-header { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; margin-left: 5px; }
        .section-icon { width: 20px; height: 20px; fill: var(--pink); }
        .section-title { font-size: 0.95rem; font-weight: bold; opacity: 0.8; }
        
        .quick-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
        .quick-btn {
            background: var(--white); border: 2px solid #F4F4F4; border-radius: 20px;
            padding: 15px 5px; display: flex; flex-direction: column; align-items: center;
            gap: 8px; cursor: pointer; transition: all 0.2s; box-shadow: var(--shadow-btn);
            position: relative; overflow: hidden;
        }
        .quick-btn::after { content: ''; position: absolute; top: -10px; right: -10px; width: 30px; height: 30px; background: var(--yellow); border-radius: 50%; opacity: 0.2; }
        .quick-btn svg { width: 32px; height: 32px; }
        .quick-btn small { font-size: 0.8rem; font-weight: bold; color: #777; }
        .quick-btn:active { transform: scale(0.92); border-color: var(--pink); background: #FFF0F5; }

        .input-wrapper { position: relative; }
        textarea {
            width: 100%; height: 100px; border: 3px solid #EAEAEA; border-radius: 24px;
            padding: 16px; font-size: 1rem; font-family: inherit; resize: none;
            background: #FAFAFA; transition: 0.3s; outline: none; color: var(--dark);
        }
        textarea:focus { border-color: var(--blue); background: var(--white); box-shadow: 0 0 0 4px rgba(137, 207, 240, 0.2); }

        .magic-btn {
            position: absolute; bottom: 10px; right: 10px;
            background: linear-gradient(135deg, #FFF59D 0%, #FFCC80 100%);
            border: none; border-radius: 20px; padding: 6px 12px;
            font-size: 0.75rem; font-weight: bold; color: #5D4037;
            cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.1);
            display: flex; align-items: center; gap: 4px; transition: all 0.2s; z-index: 10;
        }
        .magic-btn:active { transform: scale(0.95); }
        .magic-btn svg { width: 14px; height: 14px; }
        .magic-btn.loading { opacity: 0.7; cursor: wait; }

        .sound-scroller { display: flex; gap: 10px; overflow-x: auto; padding: 4px; scrollbar-width: none; }
        .sound-scroller::-webkit-scrollbar { display: none; }
        .sound-chip {
            flex: 0 0 auto; padding: 8px 18px; background: #F8F8F8; border-radius: 20px;
            font-size: 0.85rem; color: #888; cursor: pointer; border: 2px solid transparent;
            transition: 0.2s; display: flex; align-items: center; gap: 5px;
        }
        .sound-chip.active { background: var(--yellow); color: var(--dark); border-color: #FBC02D; transform: scale(1.05); font-weight: bold; box-shadow: 0 4px 10px rgba(255, 241, 118, 0.4); }

        .control-panel { display: flex; gap: 12px; margin-top: 5px; }
        .btn-main {
            flex: 2; background: var(--pink); color: white; border: none; padding: 0 20px;
            height: 64px; border-radius: 24px; font-size: 1.1rem; font-weight: 800;
            display: flex; align-items: center; justify-content: center; gap: 10px;
            box-shadow: 0 6px 0 #FF8BA0; cursor: pointer; transition: all 0.1s;
        }
        .btn-main:active { transform: translateY(6px); box-shadow: 0 0 0 #FF8BA0; }
        
        .btn-panic {
            flex: 1; background: var(--white); border: 3px solid var(--danger); color: var(--danger);
            border-radius: 24px; font-size: 0.85rem; font-weight: bold;
            display: flex; flex-direction: column; align-items: center; justify-content: center;
            cursor: pointer; transition: 0.3s; box-shadow: 0 6px 0 #FFDCDC; height: 64px;
        }
        .btn-panic:active { transform: translateY(6px); box-shadow: 0 0 0 #FFDCDC; }
        .btn-panic.active { background: var(--danger); color: white; border-color: var(--danger); animation: shake 0.5s infinite; box-shadow: 0 6px 0 #B71C1C; }

        /* --- 📔 日记页特有样式 --- */
        .diary-header { text-align: center; margin-bottom: 0.25em; }
        .diary-title { font-size: 1.2rem; font-weight: 900; letter-spacing: 1px; margin-top: 2em; margin-bottom: 0; }
        .diary-date { font-size: 0.9rem; color: #999; margin-top: 4px; font-weight: bold; }
        
        .option-label { font-size: 0.85rem; color: #888; margin-bottom: 8px; margin-left: 5px; font-weight: bold; }
        .icon-row { display: flex; gap: 12px; margin-bottom: 20px; overflow-x: auto; padding-bottom: 5px; scrollbar-width: none; }
        
        .select-icon {
            width: 48px; height: 48px; border-radius: 14px; background: #FFF;
            border: 2px solid #EEE; padding: 8px; cursor: pointer; transition: 0.2s;
            flex-shrink: 0; box-shadow: 0 2px 5px rgba(0,0,0,0.05);
        }
        .select-icon:hover { transform: translateY(-3px); }
        .select-icon.selected { border-color: var(--pink); background: #FFF0F5; transform: scale(1.1); box-shadow: 0 4px 10px rgba(255, 183, 197, 0.4); }
        .select-icon svg { width: 100%; height: 100%; }

        .btn-save {
            width: 100%; background: var(--blue); color: white; border: none; padding: 16px;
            border-radius: 20px; font-size: 1rem; font-weight: bold; margin-top: 10px;
            cursor: pointer; box-shadow: 0 6px 0 #64B5F6; transition: 0.1s;
        }
        .btn-save:active { transform: translateY(6px); box-shadow: 0 0 0 #64B5F6; }

        /* 日记列表 */
        #view-diary.view-section.active { gap: 18px; }
        .diary-list { margin-top: 0; padding-top: 10px; display: flex; flex-direction: column; gap: 15px; position: relative; }
        .diary-list::before { content: ""; position: absolute; top: 0; left: 8px; right: 8px; height: 1px; border-radius: 1px;
            background: linear-gradient(90deg, rgba(93,64,55,0.10), rgba(93,64,55,0.04)); }
        .diary-card {
            background: #FFF; border-radius: 20px; padding: 15px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.05); border: 2px solid #F8F8F8;
            position: relative; animation: slideUp 0.3s;
        }
        .card-actions { display: flex; align-items: center; gap: 8px; }
        .card-bottom { display: flex; justify-content: space-between; align-items: center; margin-top: 10px; }
        .btn-trash { width: 32px; height: 32px; border: none; border-radius: 50%; background: rgba(137, 207, 240, 0.22); color: #2f8ec9; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; }
        .btn-trash:hover { background: rgba(137, 207, 240, 0.32); }
        .btn-trash svg { width: 16px; height: 16px; fill: currentColor; }
        .card-image { width: 100%; max-height: 220px; object-fit: cover; border-radius: 12px; margin-top: 8px; border: 1px solid #F0F0F0; }
        .card-top { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 8px; border-bottom: 1px dashed #EEE; padding-bottom: 8px; }
        .card-meta { display: none; }
        .card-meta-row { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 6px; font-size: 0.82rem; color: #666; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .meta-loc { max-width: 60%; overflow: hidden; text-overflow: ellipsis; }
        .meta-sep { opacity: .4; }
        .meta-time { color: #8a8a8a; font-weight: 700; }
        .region-chip { display:none; }
        .card-content { font-size: 0.95rem; line-height: 1.5; color: var(--dark); white-space: pre-wrap; }
        .card-tags { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; white-space: nowrap; }
        .tag { font-size: 0.72rem; font-weight: 800; padding: 2px 8px; border-radius: 999px; border: 1px solid transparent; }
        .tag-weather { color: #2f8ec9; background: #F0F8FF; border-color: #E0F2FE; }
        .tag-mood { color: #d946ef; background: #FEF0FF; border-color: #F8D0FF; }
        /* 左下角淡色标签样式 */
        .tags-soft { display: flex; align-items: center; gap: 6px; }
        .tag-soft { font-size: 0.74rem; font-weight: 700; color: #8A8A8A; background: #F7F7F8; border: 1px solid #EAEAEA; padding: 2px 8px; border-radius: 999px; }
        .card-icon-small { width: 20px; height: 20px; }

        /* --- 图片上传 UI --- */
        .upload-row { display: flex; gap: 12px; align-items: center; }
        .btn-upload {
            background: #FFFFFF; color: var(--blue); border: 2px solid #A5D8FF;
            border-radius: 14px; padding: 10px 14px; height: 44px;
            font-size: 0.95rem; font-weight: 700; cursor: pointer;
            display: inline-flex; align-items: center; gap: 8px;
            box-shadow: 0 4px 0 #64B5F611; transition: transform 0.06s ease, box-shadow 0.06s ease;
        }
        .btn-upload:active { transform: translateY(2px); box-shadow: 0 0 0 transparent; }
        .btn-upload svg { width: 18px; height: 18px; fill: currentColor; }

        .upload-chip {
            flex: 1 1 auto; min-height: 44px;
            display: flex; align-items: center; gap: 10px; overflow: hidden;
            background: #F7FBFF; border: 2px dashed #E0F2FE; border-radius: 14px;
            padding: 8px 10px;
        }
        .upload-chip .placeholder { font-size: 0.85rem; color: #8AA; display: inline-flex; align-items: center; gap: 6px; }
        .preview-thumb { width: 44px; height: 44px; object-fit: cover; border-radius: 10px; border: 1px solid #E0E0E0; }
        .file-name { font-size: 0.85rem; color: #666; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
        .btn-clear { flex: 0 0 auto; width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; border: 1px solid #E6E6E6; background: #FFF; color: #999; }
        .btn-clear:active { transform: scale(0.96); }
        .btn-clear svg { width: 14px; height: 14px; fill: currentColor; }

        .toast-container { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 999; pointer-events: none; }
        .toast { background: var(--dark); color: white; padding: 12px 24px; border-radius: 50px; font-size: 0.9rem; margin-bottom: 10px; opacity: 0; transform: translateY(-20px); transition: 0.3s; display: flex; align-items: center; gap: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.2); font-weight: bold; }
        .toast.show { opacity: 1; transform: translateY(0); }

        /* 删除确认弹窗 */
        .modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.35); display: none; align-items: center; justify-content: center; z-index: 2000; padding: 16px; }
        .modal-mask.show { display: flex; }
        .modal-card { width: 100%; max-width: 380px; background: #FFF; border-radius: 24px; padding: 20px 16px 16px; box-shadow: 0 18px 50px rgba(255, 183, 197, 0.35); border: 3px solid #FFD9E5; position: relative; }
        .modal-card::before { content: ""; position: absolute; top: -10px; left: -10px; width: 50px; height: 50px; background: radial-gradient(#FFB7C5 40%, transparent 41%); opacity: 0.25; border-radius: 50%; }
        .modal-card::after { content: ""; position: absolute; bottom: -12px; right: -12px; width: 60px; height: 60px; background: radial-gradient(#89CFF0 40%, transparent 41%); opacity: 0.22; border-radius: 50%; }
        .modal-title { font-weight: 900; font-size: 1.06rem; margin-bottom: 8px; text-align: center; display: flex; align-items: center; justify-content: center; gap: 6px; color: var(--dark); }
        .modal-subtle { text-align: center; font-size: 0.9rem; color: #6b6b6b; margin-bottom: 12px; }
        .modal-row { display: flex; gap: 10px; align-items: center; margin: 10px 0; }
        .modal-input { flex: 1; height: 44px; padding: 0 12px; border-radius: 16px; border: 2px solid #FFE3EC; outline: none; background: #FFF8FB; }
        .modal-input:focus { border-color: #FFB7C5; box-shadow: 0 0 0 4px rgba(255, 183, 197, 0.25); background: #FFF; }
        .modal-actions { display: flex; justify-content: center; gap: 12px; margin-top: 10px; }
        .btn-secondary { background: #F7FBFF; border: 2px solid #E0F2FE; color: #2f8ec9; border-radius: 16px; padding: 10px 16px; font-weight: 800; }
        .btn-danger { background: linear-gradient(135deg, #FFB7C5 0%, #FFA6C9 100%); color: #FFF; border: none; border-radius: 16px; padding: 10px 18px; font-weight: 900; box-shadow: 0 6px 0 #FF8BA0; }
        .btn-danger:active { transform: translateY(2px); box-shadow: 0 0 0 transparent; }

        @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
        @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
        @keyframes shake { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(-3deg); } 75% { transform: rotate(3deg); } }
        @keyframes spin { 100% { transform: rotate(360deg); } }
    </style>
</head>
<body>
    <!-- 服务器注入的日历缓存（如果有） -->
    <script>
        window.__INITIAL_CALENDAR__ = {{ (initial_calendar or {'events': []}) | tojson | safe }};
    </script>
    <!-- 下拉刷新提示 -->
    <div class="pull-indicator" id="pullIndicator" aria-hidden="true">
        <div class="paw-wrap"><svg class="paw-svg" viewBox="0 0 24 24" fill="#FF99B3" aria-hidden="true">
            <circle cx="7" cy="7" r="3"/>
            <circle cx="12" cy="5" r="2.6"/>
            <circle cx="17" cy="7" r="3"/>
            <path d="M6,14 C6,11 9,10 12,10 C15,10 18,11 18,14 C18,17 15,19 12,19 C9,19 6,17 6,14 Z"/>
        </svg></div>
        <div class="pull-text" id="pullText">下拉刷新</div>
        <div class="pull-line" aria-hidden="true"></div>
    </div>

    <div class="bg-decoration"></div>
    <div class="toast-container" id="toastContainer"></div>
    <div id="loadingToast" style="display:none; position: fixed; top: calc(env(safe-area-inset-top) + 20px); left: 50%; transform: translateX(-50%); background: var(--dark); color: white; padding: 10px 20px; border-radius: 20px; font-size: 0.8rem; z-index: 2000; box-shadow: 0 5px 15px rgba(0,0,0,0.2);">✨ 正在读取最新日程...</div>
    <button id="calRefreshFab" class="fab-refresh" title="刷新" style="display:none">↻</button>

    <div class="app-container">
        <!-- 天气胶囊（固定左上角） -->
        <div class="weather-pill" aria-hidden="false" style="display:none">
            <div class="pill">
                <div class="info">
                    <span id="w-sheng" class="sheng">Loading</span>
                    <span id="w-city" class="city">...</span>
                    <span class="sep"></span>
                    <span id="w-cond" class="cond">--</span>
                    <span id="w-temp" class="temp">--°</span>
                </div>
                <div class="iconwrap">
                    <img id="w-icon" src="" alt="icon" />
                </div>
            </div>
        </div>
        
        <!-- ======================= 📡 页面 1: 信号发射台 (Home) ======================= -->
        <div id="view-home" class="view-section active">
            <!-- 头部 -->
            <div class="header">
                <div class="char-group">
                    <div class="char-icon" style="background:#FFF0F5; border-color:var(--pink);">
                         <svg viewBox="0 0 100 100"><path d="M20,30 Q10,10 40,20 Q60,20 90,10 Q80,30 80,40 Q90,60 80,90 Q50,100 20,90 Q10,60 20,40" fill="white" stroke="#5D4037" stroke-width="4"/><circle cx="35" cy="50" r="4" fill="#5D4037"/><circle cx="65" cy="50" r="4" fill="#5D4037"/><path d="M45,55 Q50,60 55,55" fill="none" stroke="#5D4037" stroke-width="3"/><circle cx="30" cy="60" r="6" fill="#FFB7C5" opacity="0.6"/><circle cx="70" cy="60" r="6" fill="#FFB7C5" opacity="0.6"/></svg>
                    </div>
                    <div class="char-icon" style="background:#E0F7FA; border-color:var(--blue);">
                        <svg viewBox="0 0 100 100"><path d="M20,20 L30,5 L50,15 L70,5 L80,20 Q95,30 90,80 Q50,95 10,80 Q5,30 20,20" fill="white" stroke="#5D4037" stroke-width="4"/><path d="M20,20 L30,5 L50,25 L70,5 L80,20" fill="#89CFF0"/><path d="M20,20 L30,5 L50,15 L70,5 L80,20" fill="none" stroke="#5D4037" stroke-width="4"/><circle cx="35" cy="50" r="4" fill="#5D4037"/><circle cx="65" cy="50" r="4" fill="#5D4037"/><path d="M45,58 Q50,63 55,58" fill="none" stroke="#5D4037" stroke-width="3"/></svg>
                    </div>
                    <div class="char-icon" style="background:#FFF9C4; border-color:var(--yellow);">
                        <svg viewBox="0 0 100 100"><path d="M25,30 L20,5 Q35,5 40,30 M75,30 L80,5 Q65,5 60,30" fill="#FFF59D" stroke="#5D4037" stroke-width="4"/><circle cx="50" cy="55" r="35" fill="#FFF59D" stroke="#5D4037" stroke-width="4"/><circle cx="38" cy="50" r="4" fill="#5D4037"/><circle cx="62" cy="50" r="4" fill="#5D4037"/><path d="M45,58 L50,63 L55,58" fill="none" stroke="#5D4037" stroke-width="3"/></svg>
                    </div>
                </div>
                <div class="license-badge">
                    <div class="license-header">草むしり検定</div>
                    <div class="license-body">5级合格 🏅</div>
                </div>
            </div>

            <!-- 状态 + 天气（来自 /api/status） -->
            <div class="status-card" id="statusCard">
                <div class="status-row">
                    <span class="status-dot status-idle" id="statusDot"></span>
                    <span class="status-text" id="statusText">他的状态：空闲</span>
                    <span class="status-sep"></span>
                    <span class="status-weather" id="statusWeather">
                        <span class="sw-prefix" id="statusWeatherPrefix">他的天气：</span>
                        <span class="sw-window" id="statusWeatherWindow"><span id="statusWeatherText">--° --</span></span>
                    </span>
                </div>
            </div>

            <!-- 快捷区 -->
            <div>
                <div class="section-header">
                    <svg class="section-icon" viewBox="0 0 24 24"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>
                    <div class="section-title">一键 BiuBiu</div>
                </div>
                <div class="quick-grid">
                    <button class="quick-btn" onclick="quickSend(this,'哼！生气了！💢', 'glass')">
                        <svg viewBox="0 0 100 100"><path d="M45,80 L45,30 M55,80 L55,30" stroke="#FFB7C5" stroke-width="8" stroke-linecap="round"/><path d="M30,30 Q50,50 70,30" fill="none" stroke="#FFB7C5" stroke-width="8" stroke-linecap="round"/><path d="M30,30 L30,20 M70,30 L70,20" stroke="#FFB7C5" stroke-width="8" stroke-linecap="round"/></svg><small>生气了</small>
                    </button>
                    <button class="quick-btn" onclick="quickSend(this,'饿饿饭饭 🍜', 'birdsong')">
                        <svg viewBox="0 0 100 100"><path d="M20,60 Q50,90 80,60" fill="#FFF" stroke="#5D4037" stroke-width="3"/><path d="M25,50 Q30,30 35,50 M45,50 Q50,30 55,50 M65,50 Q70,30 75,50" fill="none" stroke="#FFD700" stroke-width="3"/><rect x="35" y="45" width="10" height="10" fill="#FFB7C5" rx="2"/><path d="M20,60 L80,60" stroke="#5D4037" stroke-width="3"/></svg><small>饿饿</small>
                    </button>
                    <button class="quick-btn" onclick="quickSend(this,'亲亲 😘', 'chime')">
                        <svg viewBox="0 0 100 100"><path d="M50,80 Q90,50 90,30 Q90,10 70,10 Q50,10 50,30 Q50,10 30,10 Q10,10 10,30 Q10,50 50,80" fill="#FF6B6B" stroke="none"/><path d="M60,20 L70,10 M80,25 L90,20" stroke="#FF6B6B" stroke-width="3" stroke-linecap="round"/></svg><small>亲亲</small>
                    </button>
                    <button class="quick-btn" onclick="quickSend(this,'开门开门！🏠', 'doorbell')">
                        <svg viewBox="0 0 100 100"><path d="M20,50 L50,20 L80,50 L80,90 L20,90 Z" fill="#FFF9C4" stroke="#5D4037" stroke-width="3"/><rect x="40" y="60" width="20" height="30" fill="#8D6E63"/></svg><small>开门</small>
                    </button>
                    <button class="quick-btn" onclick="quickSend(this,'想你了！🥺', 'minuet')">
                        <svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="35" fill="#E0F7FA" stroke="#5D4037" stroke-width="3"/><path d="M35,45 Q40,40 45,45 M55,45 Q60,40 65,45" fill="none" stroke="#5D4037" stroke-width="3"/><circle cx="30" cy="55" r="3" fill="#89CFF0"/><circle cx="70" cy="55" r="3" fill="#89CFF0"/></svg><small>想你了</small>
                    </button>
                    <button class="quick-btn" onclick="quickSend(this,'在干嘛？👀', 'tweet')">
                        <svg viewBox="0 0 100 100"><rect x="20" y="35" width="60" height="40" rx="5" fill="#90A4AE" stroke="#5D4037" stroke-width="3"/><circle cx="50" cy="55" r="15" fill="#FFF" stroke="#5D4037" stroke-width="3"/><rect x="65" y="25" width="10" height="10" fill="#5D4037"/></svg><small>在干嘛</small>
                    </button>
                </div>
            </div>

            <!-- 输入区 -->
            <div>
                <div class="section-header">
                    <svg class="section-icon" viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>
                    <div class="section-title">悄悄话</div>
                </div>
                <div class="input-wrapper">
                    <textarea id="msgInput" placeholder="在此输入文字..."></textarea>
                    <button class="magic-btn" id="magicBtn" onclick="generateMagicMessage()">
                        <svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.93 12.08 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.93 3.08-2.15 4.1z"/></svg>
                        <span>✨ 魔法润色</span>
                    </button>
                </div>
            </div>

            <!-- 铃声区 -->
            <div>
                <div class="section-header">
                    <svg class="section-icon" viewBox="0 0 24 24"><path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"/></svg>
                    <div class="section-title">铃声心情</div>
                </div>
                <div class="sound-scroller" id="soundList">
                    <div class="sound-chip active" data-sound="">🔕 静音</div>
                    <div class="sound-chip" data-sound="minuet">🎹 优雅</div>
                    <div class="sound-chip" data-sound="birdsong">🐦 清晨</div>
                    <div class="sound-chip" data-sound="glass">🥂 提示</div>
                    <div class="sound-chip" data-sound="horn">📣 喇叭</div>
                    <div class="sound-chip" data-sound="noir">🕵️ 紧张</div>
                    <div class="sound-chip" data-sound="alarm">🚨 闹钟</div>
                    <div class="sound-chip" data-sound="bloom">✨ 绽放</div>
                    <div class="sound-chip" data-sound="sherwoodforest">🌲 森林</div>
                </div>
            </div>

            <!-- 操作区 -->
            <div class="control-panel">
                <button class="btn-main" onclick="sendCustom()">
                    <svg width="24" height="24" viewBox="0 0 24 24" fill="white"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
                    <span>发送信号</span>
                </button>
                <button class="btn-panic" id="panicBtn" onclick="openPanicDialog()">
                    <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" id="panicIconSvg"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>
                    <span id="panicText" style="margin-top:4px">讨伐模式</span>
                </button>
            </div>
        </div>

        <!-- ======================= 📔 页面 2: 恋爱日记 (Diary) ======================= -->
        <div id="view-diary" class="view-section">

            <!-- 顶部小标题 -->
            <div class="diary-header">
                <h1 class="diary-title">交换日记</h1>
            </div>

            <!-- 心情选择 -->
            <div>
                <div class="option-label">今日心情</div>
                <div class="icon-row" id="moodSelect">
                    <div class="select-icon" data-val="开心 😄">
                        <svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#FFF9C4" stroke="#FBC02D" stroke-width="3"/><circle cx="35" cy="45" r="4" fill="#333"/><circle cx="65" cy="45" r="4" fill="#333"/><path d="M35,65 Q50,80 65,65" fill="none" stroke="#333" stroke-width="3"/></svg>
                    </div>
                    <div class="select-icon" data-val="爱你 💖">
                        <svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#FFEBEE" stroke="#EF9A9A" stroke-width="3"/><path d="M30,45 L35,50 L40,40 M60,40 L65,50 L70,45" stroke="#E57373" stroke-width="3" fill="none"/><path d="M50,80 Q90,50 90,30 Q90,10 70,10 Q50,10 50,30 Q50,10 30,10 Q10,10 10,30 Q10,50 50,80" fill="#EF5350" transform="scale(0.3) translate(115,150)"/></svg>
                    </div>
                    <div class="select-icon" data-val="累累 💤">
                        <svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#E1F5FE" stroke="#81D4FA" stroke-width="3"/><path d="M30,50 L40,50 M60,50 L70,50" stroke="#333" stroke-width="3"/><circle cx="80" cy="30" r="5" fill="#B3E5FC"/><circle cx="90" cy="20" r="3" fill="#B3E5FC"/></svg>
                    </div>
                    <div class="select-icon" data-val="生气 💢">
                        <svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#FFEBEE" stroke="#FFCDD2" stroke-width="3"/><path d="M30,40 L45,50 M70,40 L55,50" stroke="#333" stroke-width="3"/><path d="M40,70 Q50,60 60,70" fill="none" stroke="#333" stroke-width="3"/><path d="M20,30 L30,20 M30,30 L20,20" stroke="#E53935" stroke-width="3"/></svg>
                    </div>
                </div>
            </div>

            <!-- 图片上传 -->
            <div>
                <div class="option-label">添加照片（可选）</div>
                <input type="file" id="diaryImage" accept="image/*" style="display:none" />
                <div class="upload-row">
                    <button type="button" class="btn-upload" onclick="document.getElementById('diaryImage').click()">
                        <svg viewBox="0 0 24 24"><path d="M9 2L7.17 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V6a2 2 0 00-2-2h-3.17L15 2H9zm3 15a5 5 0 110-10 5 5 0 010 10zm0-2.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"/></svg>
                        <span>添加照片</span>
                    </button>
                    <div id="diaryPreview" class="upload-chip">
                        <span id="diaryPreviewText" class="placeholder">
                            <svg viewBox="0 0 24 24" width="16" height="16" style="fill:#8AA"><path d="M9 2L7.17 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V6a2 2 0 00-2-2h-3.17L15 2H9zm3 15a5 5 0 110-10 5 5 0 010 10zm0-2.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"/></svg>
                            未选择
                        </span>
                    </div>
                </div>
            </div>

            <div class="input-wrapper">
                <textarea id="diaryInput" placeholder="互相分享下自己的生活吧～也可以分享照片哦" style="height:150px;"></textarea>
            </div>

            <button class="btn-save" onclick="saveDiary()">✨ 发布并通知</button>

            <!-- 历史日记 -->
            <div class="diary-list" id="diaryList">
                <!-- 动态生成 -->
            </div>
        </div>

        <!-- ======================= 📅 页面 3: 日历 (Calendar) ======================= -->
        <div id="view-calendar" class="view-section">
            <div class="calendar-header">
                <div class="cal-head-top">
                    <div class="cal-month" id="calMonth">Month</div>
                    <div style="font-size:12px;color:#9aa0a6">接入 iCal · 3天视图</div>
                </div>
                <div class="cal-days-3" id="calDaysHeader"></div>
            </div>
            <div class="calendar-body" style="margin-top:10px;">
                <div class="cal-scroll" id="calScroll">
                    <div class="cal-grid">
                        <div class="cal-timebar" id="calTimebar"></div>
                        <div class="cal-cols">
                            <div class="cal-lines" id="calLines"></div>
                            <div class="cal-col" id="calCol0">
                                <div class="cal-now" id="calNow" style="top:300px;"></div>
                            </div>
                            <div class="cal-col" id="calCol1"></div>
                            <div class="cal-col" id="calCol2"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

    </div>

    <!-- ======================= 🧭 底部导航（移出滚动容器，防止随滚动漂移） ======================= -->
    <div class="nav-bar">
      <div class="nav-inner">
        <div class="nav-item active" data-view="home" onclick="switchView('home')">
            <svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
            <span>发射台</span>
        </div>
        <div class="nav-item" data-view="diary" onclick="switchView('diary')">
            <svg viewBox="0 0 24 24"><path d="M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2zM7 19l5-2.14L17 19V5H7v14z"/><path d="M15 7H9v2h6V7zm0 4H9v2h6v-2z"/></svg>
            <span>日记</span>
        </div>
        <div class="nav-item" data-view="calendar" onclick="switchView('calendar')">
            <svg viewBox="0 0 24 24"><path d="M19 3h-1V1h-2v2H8V1H6v2H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V5a2 2 0 00-2-2zM5 19V8h14v11H5zm2-7h5v5H7v-5z"/></svg>
            <span>日历</span>
        </div>
      </div>
    </div>

    <!-- 删除确认弹窗 -->
    <div id="deleteModal" class="modal-mask" onclick="(event.target===this)&&closeDeletePrompt()">
        <div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="delTitle">
            <div id="delTitle" class="modal-title">🥺 真的要删除我吗？</div>
            <div class="modal-subtle">此操作需要输入密码确认</div>
            <div class="modal-row">
                <input type="password" id="deletePassword" class="modal-input" placeholder="输入密码" onkeydown="if(event.key==='Enter'){confirmDelete()}" />
            </div>
            <div class="modal-actions">
                <button class="btn-secondary" onclick="closeDeletePrompt()">先不要</button>
                <button class="btn-danger" onclick="confirmDelete()">确认删除</button>
            </div>
        </div>
    </div>

    <!-- 讨伐模式弹窗 -->
    <div id="panicModal" class="modal-mask" onclick="(event.target===this)&&closePanicDialog()">
        <div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="panicTitle">
            <div id="panicTitle" class="modal-title">🔥 紧急讨伐</div>
            <div class="modal-subtle">选择一种方式通知对方，忽略勿扰</div>
            <div style="background:#f3f4f6; color:#666; border-radius:12px; padding:10px 12px; font-size:13px; line-height:1.5; margin:8px 0 12px;">
                低声呼叫：取决于对方铃声音量，约10秒内收到；<br/>
                强制警报：无视任何模式，约3秒内收到，优先级更高。
            </div>
            <div class="modal-actions">
                <button class="btn-secondary" onclick="panicCallOnce()">📞 低声呼叫</button>
                <button class="btn-danger" onclick="panicForceAlarm()">🚨 强制警报</button>
            </div>
            <div class="modal-actions" style="margin-top:8px">
                <button class="btn-secondary" onclick="closePanicDialog()">取消</button>
            </div>
        </div>
    </div>

    <script>
        // === 📐 运行时布局修正（根据底部导航真实高度自适应内容区） ===
        function adjustContainerLayout() {
            const nav = document.querySelector('.nav-bar');
            const container = document.querySelector('.app-container');
            if (!nav || !container) return;
            const navH = nav.offsetHeight || 0; // 已包含安全区 padding
            const gap = parseInt(getComputedStyle(container).getPropertyValue('--nav-gap')) || 0;
            container.style.bottom = (navH + gap) + 'px';
        }
        window.addEventListener('DOMContentLoaded', adjustContainerLayout);
        window.addEventListener('load', adjustContainerLayout);
        window.addEventListener('resize', adjustContainerLayout);
        window.addEventListener('orientationchange', adjustContainerLayout);
        // iOS Safari 底部工具栏显隐会改变可视视口高度，使用 visualViewport 跟随调整
        if (window.visualViewport) {
            window.visualViewport.addEventListener('resize', adjustContainerLayout);
        }
        // 绑定导航点击（兜底，避免内联失效）
        document.addEventListener('DOMContentLoaded', () => {
            document.querySelectorAll('.nav-item').forEach(el => {
                el.addEventListener('click', (ev) => {
                    const v = el.getAttribute('data-view');
                    if (v) { ev.preventDefault(); switchView(v); }
                });
            });
            const fab = document.getElementById('calRefreshFab');
            if (fab) fab.addEventListener('click', () => { loadCalendar._silent = false; loadCalendar(); });

            // 初始视图为发射台时，启用贴底模式
            const container = document.querySelector('.app-container');
            const homeActive = document.getElementById('view-home')?.classList.contains('active');
            if (container) {
                container.classList.toggle('home-tight', !!homeActive);
                adjustContainerLayout();
            }
        });
        
        // === 🐾 下拉刷新（猫爪动画） ===
        (function initPullToRefresh(){
            const container = document.querySelector('.app-container');
            const indicator = document.getElementById('pullIndicator');
            const text = document.getElementById('pullText');
            if (!container || !indicator || !text) return;
            let pulling = false, engaged = false, startY = 0, dist = 0, refreshing = false;
            const maxPull = 160, threshold = 110, slop = 14; // 提升阈值 & 手势容差

            function setTransform(v){ container.style.transform = `translateY(${v}px)`; }
            function resetTransform(){ container.style.transition = 'transform .2s ease'; setTransform(0); setTimeout(()=>{container.style.transition='';}, 220); }

            container.addEventListener('touchstart', (e) => {
                if (refreshing) return;
                if (container.scrollTop > 0) return; // 只有到顶才允许
                pulling = true; engaged = false; dist = 0; startY = e.touches[0].clientY;
                indicator.classList.remove('show');
                text.textContent = '下拉刷新';
                indicator.querySelector('.paw-svg').classList.remove('pull-rotate');
            }, {passive: true});

            container.addEventListener('touchmove', (e) => {
                if (!pulling) return;
                const y = e.touches[0].clientY; const dy = y - startY;
                if (dy <= 0) return; // 只处理下拉
                if (!engaged && dy <= slop) return; // 未超过手势容差
                if (!engaged) {
                    engaged = true;
                    indicator.classList.add('show');
                }
                // 当顶部且下拉时阻止默认滚动，改为下拉位移
                e.preventDefault();
                dist = Math.min(maxPull, (dy - slop) * 0.6);
                setTransform(dist);
                text.textContent = dist >= threshold ? '释放刷新' : '下拉刷新';
            }, {passive: false});

            async function doRefresh(){
                refreshing = true;
                // 锁定到阈值高度，播放猫爪旋转
                container.style.transition = 'transform .15s ease';
                setTransform(threshold);
                indicator.querySelector('.paw-svg').classList.add('pull-rotate');
                text.textContent = '刷新中...';
                try {
                    // 根据当前视图刷新对应数据
                    const isDiary = document.getElementById('view-diary').classList.contains('active');
                    const isCalendar = document.getElementById('view-calendar').classList.contains('active');
                    if (isDiary) {
                        await Promise.allSettled([renderDiaries(), loadWeather()]);
                    } else if (isCalendar) {
                        ensureCalendarInited();
                        await Promise.allSettled([loadCalendar()]);
                    } else {
                        await Promise.allSettled([loadRemoteStatus()]);
                    }
                } finally {
                    // 轻微延迟以显示动画手感
                    await new Promise(r=>setTimeout(r, 300));
                    container.style.transition = 'transform .25s ease';
                    setTransform(0);
                    setTimeout(()=>{
                        container.style.transition = '';
                        indicator.classList.remove('show');
                        indicator.querySelector('.paw-svg').classList.remove('pull-rotate');
                        refreshing = false; pulling = false; dist = 0;
                    }, 260);
                }
            }

            function onEnd(){
                if (!pulling) return;
                pulling = false;
                if (!engaged) { indicator.classList.remove('show'); return; }
                if (dist >= threshold && !refreshing) {
                    doRefresh();
                } else {
                    resetTransform();
                    setTimeout(()=>indicator.classList.remove('show'), 180);
                }
            }

            container.addEventListener('touchend', onEnd);
            container.addEventListener('touchcancel', onEnd);
        })();
        // === 🛠️ 配置区（改为后端处理密钥与推送）===
        // 所有敏感信息与外部 API 调用均由后端 /api/* 代理
        
        // === 📦 全局状态 ===
        let selectedSound = "";
        let panicInterval = null; // 不再使用循环轰炸，但保留变量以防后续扩展
        let isPanicMode = false;  // 弹窗版本不需要状态切换
        let diaryWeather = "";
        let diarySheng = "";
        let diaryCity = "";
        let diaryRegion = "";
        let diaryMood = "开心 😄";

        // === 🚦 视图切换 ===
        function switchView(viewName) {
            document.querySelectorAll('.view-section').forEach(el => el.classList.remove('active'));
            document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));

            const view = document.getElementById(`view-${viewName}`);
            if (view) view.classList.add('active');
            const nav = document.querySelector(`.nav-item[data-view="${viewName}"]`);
            if (nav) nav.classList.add('active');

            // 天气胶囊仅在日记页显示
            const pill = document.querySelector('.weather-pill');
            if (pill) pill.style.display = (viewName === 'diary') ? 'block' : 'none';

            // 允许容器在所有视图上下滚动（解除之前的锁定）
            const container = document.querySelector('.app-container');
            if (container) container.classList.remove('no-scroll');

            // 悬浮刷新仅在日历页显示
            const fab = document.getElementById('calRefreshFab');
            if (fab) fab.style.display = (viewName === 'calendar') ? 'flex' : 'none';

            // 进入视图时各自刷新
            if (viewName === 'home') {
                loadRemoteStatus();
            } else if (viewName === 'diary') {
                loadWeather();
            } else if (viewName === 'calendar') {
                ensureCalendarInited();
                const sc = document.getElementById('calScroll');
                if (sc) sc._scrolledOnce = false;
                // 立即滚到“当前时间”附近，避免等待网络返回
                try { updateNowLine(); } catch(_) {}
                loadCalendar._silent = false;
                loadCalendar();
            }

            // 贴底模式：仅发射台启用，使内容更靠近底栏
            try {
                const container = document.querySelector('.app-container');
                if (container) container.classList.toggle('home-tight', viewName === 'home');
                adjustContainerLayout();
            } catch (_) {}

            // 触感反馈
            if(navigator.vibrate) navigator.vibrate(10);
        }

        // === 📅 日期显示 ===
        function updateDate() {
            const el = document.getElementById('currentDate');
            if (!el) return;
            const now = new Date();
            const fmt = new Intl.DateTimeFormat('zh-CN', { timeZone: DISPLAY_TZ, year: 'numeric', month: 'numeric', day: 'numeric', weekday: 'short' });
            const parts = Object.fromEntries(fmt.formatToParts(now).map(p=>[p.type,p.value]));
            el.innerText = `${parts.year}.${parts.month}.${parts.day} ${parts.weekday}`;
        }
        updateDate();

        // === ☁️ 天气加载（后端代理） ===
        async function getClientIP() {
            try {
                const r = await fetch('https://api.ipify.org?format=json', { cache: 'no-store' });
                const j = await r.json();
                if (j && j.ip) return j.ip;
            } catch (_) {}
            return '';
        }

        async function loadWeather() {
            try {
                const cip = await getClientIP();
                // 先用访问者 IP 预热一次（no-cors，不读取数据）
                try {
                    const ures = await fetch('/api/weather/prime_url' + (cip?`?ip=${encodeURIComponent(cip)}`:''));
                    const udata = await ures.json();
                    if (ures.ok && udata && udata.ok && udata.url) {
                        // 触发一次访问者直连
                        await fetch(udata.url, { mode: 'no-cors', cache: 'no-store' }).catch(()=>{});
                        // 稍作等待，给对端记录 IP 的时间
                        await new Promise(r => setTimeout(r, 200));
                    }
                } catch (_) {}

                const res = await fetch('/api/weather' + (cip?`?ip=${encodeURIComponent(cip)}`:''));
                const data = await res.json();
                if (res.ok && data && (data.code === 200 || data.weather1 || data.temperature || data.place)) {
                    const sheng = data.sheng || '';
                    const city = data.shi || data.place || '未知';
                    const cond = data.weather1 || data.weather || '--';
                    const temp = (data.nowinfo && data.nowinfo.temperature) ? `${data.nowinfo.temperature}°` : (data.temperature ? `${data.temperature}°` : '--°');
                    const shengEl = document.getElementById('w-sheng');
                    const cityEl = document.getElementById('w-city');
                    if (shengEl) shengEl.innerText = sheng;
                    if (cityEl) cityEl.innerText = city;
                    document.getElementById('w-cond').innerText = cond;
                    document.getElementById('w-temp').innerText = temp;
                    const icon = document.getElementById('w-icon');
                    if (data.weather1img) { icon.src = data.weather1img; icon.style.display = 'block'; }
                    diaryWeather = cond; // 发布时使用此天气
                    diarySheng = sheng; diaryCity = city; diaryRegion = `${sheng}${city?(' '+city):''}`.trim();
                }
            } catch (e) { console.error('Weather load error', e); }
        }
        loadWeather();

        // === 📅 日历（iCal 后端渲染） ===
        const CAL_START_HOUR = 0;
        const CAL_END_HOUR = 24;
        const DISPLAY_TZ = 'Asia/Shanghai';
        let calInited = false;

        function ensureCalendarInited(){
            if (calInited) return;
            const timebar = document.getElementById('calTimebar');
            const lines = document.getElementById('calLines');
            if (!timebar || !lines) return;
            // 左侧时间刻度与横线
            timebar.innerHTML = '';
            lines.innerHTML = '';
            for (let h = CAL_START_HOUR; h <= CAL_END_HOUR; h++) {
                const t = document.createElement('div');
                t.className = 'cal-time';
                t.textContent = (h < 10 ? '0'+h : h) + ':00';
                timebar.appendChild(t);
                if (h !== CAL_END_HOUR){
                    const l = document.createElement('div');
                    l.className = 'cal-line';
                    lines.appendChild(l);
                }
            }
            // 头部日期
            buildCalHeader();
            calInited = true;
        }

        function buildCalHeader(){
            const now = new Date();
            const month = now.toLocaleString('zh-CN', { month: 'long', timeZone: DISPLAY_TZ });
            const calMonthEl = document.getElementById('calMonth');
            if (calMonthEl) calMonthEl.textContent = month;
            const daysHeader = document.getElementById('calDaysHeader');
            if (daysHeader){
                daysHeader.innerHTML = '';
                const week = ['日','一','二','三','四','五','六'];
                for (let i=0;i<3;i++){
                    const d = new Date(now);
                    d.setDate(now.getDate()+i);
                    const wrapper = document.createElement('div');
                    wrapper.className = 'cal-day-h' + (i===0?' active':'');
                    const weekday = d.toLocaleString('zh-CN', { timeZone: DISPLAY_TZ, weekday: 'short' });
                    const daynum = d.toLocaleString('zh-CN', { timeZone: DISPLAY_TZ, day: 'numeric' });
                    wrapper.innerHTML = `<div class="cal-day-name">${weekday}</div><div class="cal-day-num">${daynum}</div>`;
                    daysHeader.appendChild(wrapper);
                }
            }
        }

        function classForEvent(title){
            const t = (title||'').toLowerCase();
            if (t.includes('会') || t.includes('meet') || t.includes('课') ) return 'evt-blue';
            if (t.includes('休') || t.includes('假') || t.includes('餐') || t.includes('饭')) return 'evt-yellow';
            return 'evt-pink';
        }

        function minutesFromStart(dateObj){
            const parts = new Intl.DateTimeFormat('zh-CN', { timeZone: DISPLAY_TZ, hour: '2-digit', minute: '2-digit', hour12: false }).formatToParts(dateObj);
            const h = parseInt(parts.find(p=>p.type==='hour')?.value || '0', 10);
            const m = parseInt(parts.find(p=>p.type==='minute')?.value || '0', 10);
            let total = (h - CAL_START_HOUR) * 60 + m;
            const max = (CAL_END_HOUR - CAL_START_HOUR) * 60;
            if (total < 0) total = 0;
            if (total > max) total = max;
            return total;
        }

        function timeLabel(s, e){
            const fmt = new Intl.DateTimeFormat('zh-CN', { timeZone: DISPLAY_TZ, hour: '2-digit', minute: '2-digit', hour12: false });
            const fs = fmt.format(s);
            const fe = fmt.format(e);
            return `${fs} - ${fe}`;
        }

        function clearCalendarCols(){
            ['calCol0','calCol1','calCol2'].forEach(id => {
                const el = document.getElementById(id);
                if (!el) return;
                // 保留 now line 在 day0
                if (id === 'calCol0'){
                    el.innerHTML = '<div class="cal-now" id="calNow" style="top:300px;"></div>';
                } else {
                    el.innerHTML = '';
                }
            });
        }

        function renderCalendar(data){
            ensureCalendarInited();
            clearCalendarCols();
            const events = (data && data.events) || [];
            const startDay = new Date(); startDay.setHours(0,0,0,0);
            for (const e of events){
                try {
                    const s = new Date(e.start);
                    const en = new Date(e.end);
                    // Determine which day columns this spans (0..2)
                    for (let di=0; di<3; di++){
                        const dayStart = new Date(startDay); dayStart.setDate(startDay.getDate()+di);
                        const dayEnd = new Date(dayStart); dayEnd.setDate(dayStart.getDate()+1);
                        if (s < dayEnd && en > dayStart){
                            const col = document.getElementById('calCol'+di);
                            if (!col) continue;
                            const card = document.createElement('div');
                            const cat = classForEvent(e.summary);
                            card.className = `evt ${cat}`;
                            // Placement
                            let visStart = new Date(Math.max(s.getTime(), new Date(dayStart).setHours(CAL_START_HOUR,0,0,0)));
                            let visEnd = new Date(Math.min(en.getTime(), new Date(dayStart).setHours(CAL_END_HOUR,0,0,0)));
                            if (e.all_day){
                                card.style.top = '4px';
                                card.style.height = '26px';
                                card.style.left = '3%';
                                card.style.width = '94%';
                                card.innerHTML = `<div class="t">${e.summary || '(全天)'}</div><div class="tm">全天</div>`;
                                card.addEventListener('click', ()=>{
                                    showEventDetail(e.summary || '(全天)', '全天', e.location || '');
                                });
                            } else {
                                const top = minutesFromStart(visStart);
                                const bottom = minutesFromStart(visEnd);
                                const h = Math.max(20, bottom - top);
                                card.style.top = `${top}px`;
                                card.style.height = `${h}px`;
                                const tm = timeLabel(s,en);
                                card.innerHTML = `<div class="t">${e.summary || '(无标题)'}</div><div class="tm">${tm}</div>`;
                                card.addEventListener('click', ()=>{
                                    showEventDetail(e.summary || '(无标题)', tm, e.location || '');
                                });
                            }
                            col.appendChild(card);
                        }
                    }
                } catch(_) { /* noop */ }
            }
            updateNowLine();
        }

        function updateNowLine(){
            const nowLine = document.getElementById('calNow');
            if (!nowLine) return;
            const now = new Date();
            const top = minutesFromStart(now);
            nowLine.style.top = `${top}px`;
            // 初次进入时滚动到“当前时间”附近
            const sc = document.getElementById('calScroll');
            if (sc && !sc._scrolledOnce){
                const target = top; // 以北京时间“此刻”作为目标
                const viewport = sc.clientHeight || 400;
                // 将当前时间线放到视口约 40% 处，观感更自然
                const offset = Math.max(0, target - Math.floor(viewport * 0.4));
                sc.scrollTop = offset;
                sc._scrolledOnce = true;
            }
        }
        setInterval(updateNowLine, 60*1000);

        // 定时刷新最新 ICS（轻量，不显示提示）
        setInterval(() => {
            if (document.getElementById('view-calendar')?.classList.contains('active')){
                loadCalendar._silent = true;
                loadCalendar();
            }
        }, 60000);

        async function loadCalendar(){
            // 兼容：优先尝试 /api/ics/latest + ical.js；失败时退回旧缓存模式
            try {
                const toast = document.getElementById('loadingToast');
                if (!loadCalendar._silent) {
                    toast.textContent = '✨ 正在读取最新日程...';
                    toast.style.display = 'block';
                }
                const res = await fetch('/api/ics/latest', { cache: 'no-store' });
                const data = await res.json();
                if (res.ok && data && data.ok && data.ics){
                    parseICSAndRender(data.ics, data.filename);
                    return;
                }
            } catch (_) {}
            // fallback
            try {
                const res = await fetch('/api/calendar', { cache: 'no-store' });
                const data = await res.json();
                if (res.ok && data && data.ok){
                    renderCalendar(data);
                }
            } catch (_) {}
        }

        function parseICSAndRender(icsText, filename){
            try {
                const jcalData = ICAL.parse(icsText);
                const comp = new ICAL.Component(jcalData);
                const vevents = comp.getAllSubcomponents('vevent');

                // 仅展开并渲染「今天起三天」窗口内的实例
                const windowStart = new Date();
                windowStart.setHours(0,0,0,0);
                const windowEnd = new Date(windowStart);
                windowEnd.setDate(windowStart.getDate() + 3);

                const items = [];
                // 注册 ICS 中的时区定义，确保 toUnixTime 正确考虑源时区偏移
                try {
                    const tzComps = comp.getAllSubcomponents('vtimezone') || [];
                    for (const tzc of tzComps) {
                        try {
                            const tzid = tzc.getFirstPropertyValue && tzc.getFirstPropertyValue('tzid');
                            if (tzid) {
                                const tz = new ICAL.Timezone(tzc);
                                ICAL.TimezoneService.register(tzid, tz);
                            }
                        } catch(_) {}
                    }
                } catch(_) {}
                for (const v of vevents){
                    try{
                        const evt = new ICAL.Event(v);
                        const pushOne = (sDate, eDate, isAllDay) => {
                            if (!sDate || !eDate) return;
                            // 仅保留与窗口有交集的实例
                            if (sDate < windowEnd && eDate > windowStart){
                                items.push({
                                    summary: evt.summary || '',
                                    location: evt.location || '',
                                    start: sDate.toISOString(),
                                    end: eDate.toISOString(),
                                    all_day: !!isAllDay
                                });
                            }
                        };

                        const fallbackMillis = 60 * 60 * 1000; // 无 DTEND 时默认 1h
                        if (evt.isRecurring()){
                            const durSec = (()=>{ try { return evt.duration && evt.duration.toSeconds ? evt.duration.toSeconds() : null; } catch(_) { return null; } })();
                            const exp = new ICAL.RecurExpansion({ component: v, dtstart: evt.startDate });
                            let guard = 0;
                            while (true){
                                const next = exp.next();
                                if (!next) break;
                                // 使用绝对时间（Unix epoch）确保跨时区准确
                                const startMs = (typeof next.toUnixTime === 'function') ? next.toUnixTime()*1000 : (next.toJSDate()||new Date()).getTime();
                                const s = new Date(startMs);
                                let e;
                                if (durSec && durSec > 0){
                                    e = new Date(startMs + durSec*1000);
                                } else if (evt.endDate && typeof evt.endDate.toUnixTime === 'function' && typeof evt.startDate.toUnixTime === 'function'){
                                    const ms = (evt.endDate.toUnixTime() - evt.startDate.toUnixTime())*1000;
                                    e = new Date(startMs + (isFinite(ms) && ms>0 ? ms : fallbackMillis));
                                } else {
                                    e = new Date(startMs + fallbackMillis);
                                }
                                const allDay = !!(evt.startDate && evt.startDate.isDate);
                                pushOne(s, e, allDay);
                                if (s >= windowEnd) break;
                                if (++guard > 1000) break;
                            }
                        } else {
                            // 单次事件：直接用 Unix epoch
                            const sd = evt.startDate;
                            const ed = evt.endDate;
                            const s = sd ? new Date((sd.toUnixTime && sd.toUnixTime()*1000) || sd.toJSDate()) : null;
                            let e = ed ? new Date((ed.toUnixTime && ed.toUnixTime()*1000) || ed.toJSDate()) : null;
                            if (s && !e) e = new Date(s.getTime() + fallbackMillis);
                            const allDay = !!(evt.startDate && evt.startDate.isDate);
                            pushOne(s, e, allDay);
                        }
                    }catch(_){ /* ignore one event */ }
                }
                renderCalendar({ ok: true, events: items });
                const now = new Date();
                const hh = String(now.getHours()).padStart(2,'0');
                const mm = String(now.getMinutes()).padStart(2,'0');
                // 刷新完成提示：仅在非静默模式下显示一次
                if (!loadCalendar._silent) {
                    const toast = document.getElementById('loadingToast');
                    const suffix = filename ? ` (${filename})` : '';
                    toast.textContent = `${hh}:${mm} latest update` + suffix;
                    toast.style.display = 'block';
                    setTimeout(()=>{ toast.style.display='none'; }, 1600);
                }
            } catch (e) {
                console.error('ICS parse failed', e);
            }
        }

        // === 📡 远端状态（预留接口 /api/status） ===
        async function loadRemoteStatus() {
            const dot = document.getElementById('statusDot');
            const txt = document.getElementById('statusText');
            const w = document.getElementById('statusWeatherWindow');
            const wText = document.getElementById('statusWeatherText');
            const wPrefix = document.getElementById('statusWeatherPrefix');
            if (!dot || !txt || !w || !wText || !wPrefix) return;
            try {
                // 1) 状态数据（仍可由后端预留接口返回）
                let status = 'idle';
                try {
                    const res = await fetch('/api/status');
                    const data = await res.json();
                    status = (data && data.status) || 'idle';
                } catch (_) {}

                // 2) 固定 IP 天气 + 位置（183.206.163.210）
                let temp = '--°', weather = '--', place = '';
                try {
                    const wres = await fetch('/api/weather?ip=183.206.163.210', { cache: 'no-store' });
                    const wdata = await wres.json();
                    if (wres.ok && wdata && (wdata.code === 200 || wdata.weather1)) {
                        const sheng = wdata.sheng || '';
                        const city = wdata.shi || '';
                        place = (wdata.place) || `${sheng}${city?(' '+city):''}`.trim();
                        weather = wdata.weather1 || wdata.weather || '--';
                        temp = (wdata.nowinfo && wdata.nowinfo.temperature != null)
                            ? `${wdata.nowinfo.temperature}°`
                            : (wdata.temperature != null ? `${wdata.temperature}°` : '--°');
                    }
                } catch (_) {}

                const map = {
                    'idle': { label: '空闲', cls: 'status-idle' },
                    'busy': { label: '忙碌', cls: 'status-busy' },
                    'sleeping': { label: '休息', cls: 'status-sleeping' },
                    'dnd': { label: '请勿打扰', cls: 'status-dnd' }
                };
                const info = map[status] || map['idle'];
                dot.className = `status-dot ${info.cls}`;
                txt.innerText = `他的状态：${info.label}`;

                // 更新天气文本
                const fullPrefix = `他的天气：`;
                const valueText = `${temp} ${weather}${place?` · ${place}`:''}`;
                // 重置样式状态
                wText.style.transition = '';
                wText.style.transform = '';
                wText.style.display = '';
                w.classList.remove('marquee');
                wPrefix.textContent = fullPrefix;
                wText.textContent = valueText;

                // 跑马灯：不论是否溢出，都执行 3 秒动画，结束后复位（只滚动值部分）
                requestAnimationFrame(() => {
                    const cw = w.getBoundingClientRect().width || w.clientWidth;
                    const tw = wText.scrollWidth;
                    const distance = Math.max(0, tw - cw);
                    const original = valueText;
                    w.classList.add('marquee');
                    wText.style.display = 'inline-block';
                    wText.style.willChange = 'transform';
                    wText.style.transition = 'none';
                    wText.style.transform = 'translateX(0)';
                    requestAnimationFrame(() => {
                        wText.style.transition = 'transform 3s linear';
                        wText.style.transform = `translateX(${-distance}px)`;
                        setTimeout(() => {
                            wText.style.transition = '';
                            wText.style.transform = '';
                            wText.style.display = '';
                            w.classList.remove('marquee');
                            wText.textContent = original;
                        }, 3100);
                    });
                });
            } catch (e) {
                // 回退到默认空闲
                dot.className = 'status-dot status-idle';
                txt.innerText = '他的状态：空闲';
                wText.textContent = '他的天气：--° --';
            }
        }
        // 初始发射台为默认视图，加载一次状态
        loadRemoteStatus();

        // === 🎨 日记选择器逻辑（天气选择已移除）===
        document.querySelectorAll('#moodSelect .select-icon').forEach(icon => {
            icon.addEventListener('click', function() {
                document.querySelectorAll('#moodSelect .select-icon').forEach(i => i.classList.remove('selected'));
                this.classList.add('selected');
                diaryMood = this.dataset.val;
            });
        });
        document.querySelector('#moodSelect .select-icon').click(); // 默认选中第一个

        // === 📔 发布日记 ===
        // 监听图片选择并预览
        const diaryImageInput = document.getElementById('diaryImage');
        const diaryPreview = document.getElementById('diaryPreview');
        const diaryPreviewText = document.getElementById('diaryPreviewText');
        let diaryPreviewUrl = null;

        function clearDiaryImage() {
            if (diaryPreviewUrl) {
                try { URL.revokeObjectURL(diaryPreviewUrl); } catch (_) {}
            }
            diaryPreviewUrl = null;
            if (diaryImageInput) diaryImageInput.value = '';
            if (diaryPreview) {
                diaryPreview.innerHTML = '';
                diaryPreviewText.textContent = '未选择';
                diaryPreview.appendChild(diaryPreviewText);
            }
        }
        diaryImageInput?.addEventListener('change', () => {
            diaryPreview.innerHTML = '';
            const file = diaryImageInput.files && diaryImageInput.files[0];
            if (!file) { diaryPreviewText.textContent = '未选择'; diaryPreview.appendChild(diaryPreviewText); return; }
            if (diaryPreviewUrl) {
                try { URL.revokeObjectURL(diaryPreviewUrl); } catch (_) {}
            }
            diaryPreviewUrl = URL.createObjectURL(file);
            const img = document.createElement('img');
            img.className = 'preview-thumb';
            img.alt = '预览图';
            img.src = diaryPreviewUrl;
            const name = document.createElement('span');
            name.className = 'file-name';
            name.textContent = file.name;
            const clearBtn = document.createElement('button');
            clearBtn.type = 'button';
            clearBtn.className = 'btn-clear';
            clearBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M18.3 5.71a1 1 0 00-1.41 0L12 10.59 7.11 5.7a1 1 0 10-1.41 1.41L10.59 12l-4.9 4.89a1 1 0 101.41 1.41L12 13.41l4.89 4.9a1 1 0 001.41-1.41L13.41 12l4.9-4.89a1 1 0 000-1.41z"/></svg>';
            clearBtn.addEventListener('click', (e) => { e.stopPropagation(); clearDiaryImage(); });
            diaryPreview.appendChild(img);
            diaryPreview.appendChild(name);
            diaryPreview.appendChild(clearBtn);
        });

        async function saveDiary() {
            const input = document.getElementById('diaryInput');
            const content = input.value.trim();
            if(!content) {
                showToast("日记内容不能为空哦~", "error");
                return;
            }

            // 1. 组装数据（后端为准）
            const now = new Date();
            const timeStr = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`;
            const diaryEntry = {
                id: Date.now(),
                date: `${now.getMonth()+1}.${now.getDate()}`,
                time: timeStr,
                weather: diaryWeather,
                mood: diaryMood,
                content: content,
                image: '',
                region: diaryRegion
            };

            // 2. 如选择了图片，先上传
            try {
                const file = diaryImageInput && diaryImageInput.files && diaryImageInput.files[0];
                if (file) {
                    const fd = new FormData();
                    fd.append('file', file);
                    const upRes = await fetch('/api/upload', { method: 'POST', body: fd });
                    const upData = await upRes.json();
                    if (upRes.ok && upData.ok && upData.url) {
                        diaryEntry.image = upData.url;
                    }
                }
            } catch (_) {}

            // 3. 后端持久化（同步）
            try {
                const res = await fetch('/api/diary', {
                    method: 'POST', headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(diaryEntry)
                });
                const data = await res.json();
                if (!res.ok || !data.ok) throw new Error('save failed');
            } catch (e) {
                showToast('保存失败，请稍后重试', 'error');
                return;
            }

            // 4. 推送通知（后端代理）
            const pushText = `心情:${diaryMood} | 天气:${diaryWeather}\n${content.substring(0, 50)}${content.length>50?'...':''}`;
            const pushTitle = `⏰ ${timeStr} 发送了一篇日记`;
            fetch('/api/push', {
                method: 'POST', headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ title: pushTitle, text: pushText })
            }).catch(() => {});

            // 5. 从后端刷新列表
            renderDiaries();

            // 清空输入
            input.value = "";
            showToast("日记已发布并推送！✨", "success");
            // 重置图片选择
            clearDiaryImage();
        }

        // === 📜 渲染日记列表（从后端） ===
        async function renderDiaries() {
            const listEl = document.getElementById('diaryList');
            try {
                const res = await fetch('/api/diary');
                const diaries = await res.json();
                listEl.innerHTML = (Array.isArray(diaries) ? diaries : []).map(d => `
                    <div class="diary-card">
                        <div class="card-top">
                            <div class="card-meta-row">
                                ${d.region ? `<span class=\"meta-loc\">📍 ${d.region}</span>` : ''}
                                <span class=\"meta-sep\">·</span>
                                <span class=\"meta-time\">📅 ${d.date} ${d.time}</span>
                            </div>
                            
                        </div>
                        <div class="card-content">${d.content}</div>
                        ${d.image ? `<img class="card-image" src="${d.image}" alt="配图" />` : ''}
                        <div class="card-bottom">
                            <div class="tags-soft">
                                ${d.weather ? `<span class=\"tag-soft\">${d.weather}</span>` : ''}
                                ${d.mood ? `<span class=\"tag-soft\">${d.mood}</span>` : ''}
                            </div>
                            <button class="btn-trash" title="删除" onclick="openDeletePrompt(${d.id})">
                                <svg viewBox="0 0 24 24"><path d="M9 3h6l1 2h4v2H4V5h4l1-2zm-1 6h2v9H8V9zm4 0h2v9h-2V9zm-6 0h12l-1 11a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 9z"/></svg>
                            </button>
                        </div>
                    </div>
                `).join('');
            } catch (e) {
                listEl.innerHTML = '';
            }
        }
        renderDiaries(); // 初始化加载（从后端）

        // === 🗑️ 删除日记 - 密码确认 ===
        let pendingDeleteId = null;
        function openDeletePrompt(id) {
            pendingDeleteId = id;
            const modal = document.getElementById('deleteModal');
            const input = document.getElementById('deletePassword');
            modal.classList.add('show');
            setTimeout(() => input && input.focus(), 50);
        }
        function closeDeletePrompt() {
            pendingDeleteId = null;
            const modal = document.getElementById('deleteModal');
            const input = document.getElementById('deletePassword');
            if (input) input.value = '';
            modal.classList.remove('show');
        }
        async function confirmDelete() {
            const input = document.getElementById('deletePassword');
            const pwd = (input?.value || '').trim();
            if (!pendingDeleteId) { closeDeletePrompt(); return; }
            try {
                const res = await fetch('/api/diary/delete', {
                    method: 'POST', headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ id: pendingDeleteId, password: pwd })
                });
                const data = await res.json();
                if (!res.ok || !data.ok) throw new Error('forbidden');
                showToast('已删除', 'success');
                closeDeletePrompt();
                renderDiaries();
            } catch (e) {
                showToast('密码错误或删除失败', 'error');
            }
        }

        // === 🎵 铃声选择逻辑 (Home页) ===
        const chips = document.querySelectorAll('.sound-chip');
        chips.forEach(chip => {
            chip.addEventListener('click', () => {
                chips.forEach(c => c.classList.remove('active'));
                chip.classList.add('active');
                selectedSound = chip.dataset.sound;
                if (navigator.vibrate) navigator.vibrate(20);
                // 触发一次铃声心情推送（仅内容为“铃声心情”，无前缀）
                if (selectedSound) testSoundPush(selectedSound);
            });
        });

        function testSoundPush(soundName) {
            const payload = {
                title: "",
                text: "铃声心情",
                sound: soundName,
                level: '',
                path_style: 'body_only',
                channel: '' // 不使用前缀，满足你给的 URL 形式
            };
            fetch('/api/push', {
                method: 'POST', headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(payload)
            }).catch(()=>{});
        }

        // === 🔮 Gemini AI 魔法润色 ===
        async function generateMagicMessage() {
            const input = document.getElementById('msgInput');
            const originalText = input.value.trim();
            const btn = document.getElementById('magicBtn');
            const originalContent = btn.innerHTML;
            btn.innerHTML = `<svg viewBox="0 0 24 24" fill="currentColor" style="animation:spin 1s linear infinite"><path d="M12 4V2C6.48 2 2 6.48 2 12h2c0-4.41 3.59-8 8-8zm0 14c-4.41 0-8-3.59-8-8H2c0 5.52 4.48 10 10 10v-2zm10-6c0-5.52-4.48-10-10-10v2c4.41 0 8 3.59 8 8h2zm-2 0c0 4.41-3.59 8-8 8v2c5.52 0 10-4.48 10-10h-2z"/></svg> 思考中...`;
            btn.classList.add('loading');
            btn.disabled = true;

            const prompt = originalText
                ? `请把这句话改写得非常可爱、软萌，模仿吉伊卡哇(Chiikawa)里的小可爱语气，适合发给恋人撒娇。包含颜文字和Emoji。保持简短。原话：“${originalText}”`
                : `生成一句Chiikawa语气的简短撒娇话，包含颜文字(如🥺, 💦)。`;

            try {
                const response = await fetch('/api/magic', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ text: prompt })
                });
                const data = await response.json();
                if (!response.ok || !data.ok) throw new Error('API Error');
                input.value = (data.text || '').trim();
                showToast("✨ 魔法润色完成！", "success");
            } catch (error) {
                console.error(error);
                showToast("魔法失效了...请检查Key 🥺", "error");
            } finally {
                btn.innerHTML = originalContent;
                btn.classList.remove('loading');
                btn.disabled = false;
            }
        }

        // === 🚀 推送功能 (Home页) ===
        function pushToBark(text, sound = "", isLoop = false) {
            if (!text) return;
            const payload = {
                title: "", // 使用 body-only 路径，文本作为内容段
                text,
                // 显式控制是否带 sound；不再默认回落到 selectedSound
                sound: sound,
                level: isLoop ? 'timeSensitive' : '',
                path_style: 'body_only'
            };
            fetch('/api/push', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(payload)
            }).then(async (res) => {
                const data = await res.json().catch(() => ({}));
                if (!isLoop) {
                    if (res.ok && data.ok) showToast(`已发送: ${text}`, 'success');
                    else showToast('发送失败', 'error');
                }
            }).catch(() => { if (!isLoop) showToast('发送失败', 'error'); });
        }

        function quickSend(el, text, presetSound) {
            if (el) {
                el.style.transform = "scale(0.85)";
                setTimeout(() => el.style.transform = "", 150);
            }
            // 仅发送文本（使用前缀与 icon），不携带铃声
            pushToBark(text, "");
        }

        function sendCustom() {
            const input = document.getElementById('msgInput');
            const text = input.value.trim();
            if (!text) { showToast("写点什么再发吧~", "error"); input.focus(); return; }
            const btn = document.querySelector('.btn-main');
            const originalContent = btn.innerHTML;
            btn.innerHTML = "<span>🚀 发送中...</span>";
            // 1) 先发送文本（使用前缀与 icon）
            pushToBark(text, "");
            // 2) 若选择了铃声，则额外发送一次“铃声心情”（无前缀），只用于触发铃声
            if (selectedSound) {
                testSoundPush(selectedSound);
            }
            // 记录聊天到后端
            fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text }) }).catch(() => {});
            setTimeout(() => { btn.innerHTML = originalContent; }, 800);
        }

        function openPanicDialog(){
            const m = document.getElementById('panicModal');
            m.classList.add('show');
        }
        function closePanicDialog(){
            const m = document.getElementById('panicModal');
            m.classList.remove('show');
        }
        // 首次点击：低声持续铃声（call=1），仅发送一次；无前缀、无 icon
        function panicCallOnce(){
            const payload = {
                title: "",
                text: "🔥 紧急讨伐！速回电话！🔥",
                path_style: 'body_only',
                channel: '', // 禁用前缀
                call: '1'
            };
            fetch('/api/push', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) })
                .then(r=>r.json()).then(()=>{ showToast('已呼叫一次', 'success'); })
                .catch(()=>{ showToast('发送失败', 'error'); })
                .finally(()=> closePanicDialog());
        }
        // 无视勿扰：携带 critical + alarm + volume=10，无前缀但带 icon；每5秒发送一次，共3次
        function panicForceAlarm(){
            const payload = {
                title: "",
                text: "🔥 紧急讨伐！速回消息！🔥",
                sound: 'alarm',
                level: 'critical',
                volume: '10',
                path_style: 'body_only',
                channel: '', // 禁用前缀
                include_icon: true // 无前缀但需要头像
            };
            const sendOnce = () => fetch('/api/push', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).catch(()=>{});
            sendOnce(); // 立即一次
            setTimeout(sendOnce, 5000);
            setTimeout(sendOnce, 10000);
            showToast('已触发强制警报', 'success');
            closePanicDialog();
        }

        function showToast(message, type = "default") {
            const container = document.getElementById('toastContainer');
            const toast = document.createElement('div');
            toast.className = 'toast';
            let icon = "✨"; if (type === "error") icon = "🥺"; if (type === "success") icon = "💖";
            toast.innerHTML = `<span>${icon}</span> ${message}`;
            if (type === "error") toast.style.background = "var(--danger)";
            container.appendChild(toast);
            requestAnimationFrame(() => toast.classList.add('show'));
            setTimeout(() => { toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }, 2500);
        }
    </script>
    <!-- 事件详情弹层 -->
    <div id="evtModal" style="display:none;">
        <div id="evtSheet" role="dialog" aria-modal="true" aria-labelledby="evtTitle">
            <div id="evtTitle"></div>
            <div id="evtMeta"></div>
            <div id="evtActions">
                <button class="btn-secondary" id="evtOk">OK</button>
            </div>
        </div>
    </div>
    <script>
        (function(){
            const modal = document.getElementById('evtModal');
            const titleEl = document.getElementById('evtTitle');
            const metaEl = document.getElementById('evtMeta');
            const okBtn = document.getElementById('evtOk');
            function hide(){ modal.classList.remove('show'); setTimeout(()=> modal.style.display='none', 200); }
            window.showEventDetail = function(title, timestr, location){
                titleEl.textContent = title || '(无标题)';
                const loc = location ? ` · ${location}` : '';
                metaEl.textContent = timestr + loc;
                modal.style.display = 'block';
                requestAnimationFrame(()=> modal.classList.add('show'));
            };
            if (okBtn) okBtn.addEventListener('click', hide);
            modal.addEventListener('click', (e)=>{ if (e.target === modal) hide(); });
            window.addEventListener('keydown', (e)=>{ if (e.key === 'Escape') hide(); });
        })();
    </script>
</body>
</html>
