吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1417|回复: 33
上一主题 下一主题
收起左侧

[其他原创] 手机适配【热量计算器+体重记录】html网页程序

[复制链接]
跳转到指定楼层
楼主
liangzaihuigui 发表于 2026-7-25 20:28 回帖奖励
本帖最后由 liangzaihuigui 于 2026-7-26 08:04 编辑



很少在这里发帖,在别的地方养成了习惯,尽然忘了在次更新编辑,
【已更新】放在13楼,下载请移步至此!

[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes" />
    <title>热量计算+体重记录</title>
    <style>
        /* ===== CSS变量(主题色) ===== */
        :root {
            --primary-start: #ff7e5f;
            --primary-end: #feb47b;
            --primary-shadow: rgba(255, 126, 95, 0.25);
            --active-bg: #ff7e5f;
            --active-color: #fff;
        }
        /* 主题色方案 12种 */
        .theme-1 {
            --primary-start: #ff7e5f;
            --primary-end: #feb47b;
            --primary-shadow: rgba(255, 126, 95, 0.25);
            --active-bg: #ff7e5f;
        }
        .theme-2 {
            --primary-start: #11998e;
            --primary-end: #38ef7d;
            --primary-shadow: rgba(17, 153, 142, 0.25);
            --active-bg: #11998e;
        }
        .theme-3 {
            --primary-start: #2193b0;
            --primary-end: #6dd5ed;
            --primary-shadow: rgba(33, 147, 176, 0.25);
            --active-bg: #2193b0;
        }
        .theme-4 {
            --primary-start: #8E2DE2;
            --primary-end: #4A00E0;
            --primary-shadow: rgba(142, 45, 226, 0.25);
            --active-bg: #8E2DE2;
        }
        .theme-5 {
            --primary-start: #f093fb;
            --primary-end: #f5576c;
            --primary-shadow: rgba(245, 87, 108, 0.25);
            --active-bg: #f5576c;
        }
        .theme-6 {
            --primary-start: #a8e063;
            --primary-end: #56ab2f;
            --primary-shadow: rgba(86, 171, 47, 0.25);
            --active-bg: #56ab2f;
        }
        .theme-7 {
            --primary-start: #f2994a;
            --primary-end: #f2c94c;
            --primary-shadow: rgba(242, 153, 74, 0.25);
            --active-bg: #f2994a;
        }
        .theme-8 {
            --primary-start: #00c6fb;
            --primary-end: #005bea;
            --primary-shadow: rgba(0, 198, 251, 0.25);
            --active-bg: #00c6fb;
        }
        .theme-9 {
            --primary-start: #cb2d3e;
            --primary-end: #ef473a;
            --primary-shadow: rgba(203, 45, 62, 0.25);
            --active-bg: #cb2d3e;
        }
        .theme-10 {
            --primary-start: #00b09b;
            --primary-end: #96c93d;
            --primary-shadow: rgba(0, 176, 155, 0.25);
            --active-bg: #00b09b;
        }
        .theme-11 {
            --primary-start: #1a2a6c;
            --primary-end: #b21f1f;
            --primary-shadow: rgba(26, 42, 108, 0.25);
            --active-bg: #1a2a6c;
        }
        .theme-12 {
            --primary-start: #2c3e50;
            --primary-end: #3498db;
            --primary-shadow: rgba(44, 62, 80, 0.25);
            --active-bg: #2c3e50;
        }

        /* ===== 全局 ===== */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            -webkit-tap-highlight-color: transparent;
        }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
            background: #f2f4f8;
            min-height: 100vh;
            padding: 8px 10px 100px;
            color: #1a1a2e;
            overflow-x: hidden;
        }
        .app {
            max-width: 500px;
            margin: 0 auto;
            position: relative;
        }
        .card {
            background: #fff;
            border-radius: 18px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
            padding: 14px 14px;
            margin-bottom: 12px;
            transition: box-shadow 0.2s;
        }

        /* ===== 顶部栏 ===== */
        .top-bar {
            display: flex;
            align-items: center;
            justify-content: space-between;
            background: linear-gradient(135deg, var(--primary-start), var(--primary-end));
            border-radius: 18px;
            padding: 8px 16px;
            color: #fff;
            margin-bottom: 10px;
            min-height: 48px;
            transition: background 0.4s, box-shadow 0.4s;
            box-shadow: 0 4px 16px var(--primary-shadow);
        }
        .top-bar .brand {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .top-bar .brand .icon {
            font-size: 26px;
            line-height: 1;
        }
        .top-bar .brand .title {
            font-size: 18px;
            font-weight: 700;
            letter-spacing: 0.3px;
        }
        .top-bar .actions {
            display: flex;
            gap: 6px;
            align-items: center;
        }
        .top-bar .actions button {
            background: rgba(255, 255, 255, 0.20);
            border: none;
            border-radius: 30px;
            padding: 4px 12px;
            font-size: 15px;
            color: #fff;
            cursor: pointer;
            transition: background 0.2s, transform 0.1s;
            backdrop-filter: blur(2px);
            line-height: 1.4;
            white-space: nowrap;
            font-weight: 500;
        }
        .top-bar .actions button:active {
            background: rgba(255, 255, 255, 0.35);
            transform: scale(0.95);
        }

        /* ===== 日期+餐次+输入 合并卡片 ===== */
        .date-meal-row {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 6px 12px;
            margin-bottom: 12px;
            padding-bottom: 10px;
            border-bottom: 1px solid #f0f2f7;
        }
        .date-meal-row .date-left {
            display: flex;
            align-items: center;
            gap: 6px;
            font-size: 15px;
            font-weight: 600;
            color: #1a1a2e;
            flex: 1;
            white-space: nowrap;
        }
        .date-meal-row .date-left .date-icon {
            font-size: 18px;
        }
        .date-meal-row .meal-group {
            display: flex;
            gap: 4px;
            flex-wrap: wrap;
        }
        .date-meal-row .meal-group .meal-btn {
            padding: 3px 10px;
            border-radius: 30px;
            border: none;
            background: #f0f2f7;
            color: #555;
            font-size: 12px;
            font-weight: 500;
            cursor: pointer;
            transition: background 0.2s, transform 0.1s;
        }
        .date-meal-row .meal-group .meal-btn.active {
            background: var(--active-bg);
            color: var(--active-color);
            box-shadow: 0 2px 8px var(--primary-shadow);
        }
        .date-meal-row .meal-group .meal-btn:active {
            transform: scale(0.94);
        }

        /* 输入区 */
        .input-row-wrap {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 8px;
            flex-wrap: nowrap;
        }
        .input-row-wrap:last-child {
            margin-bottom: 0;
        }
        .input-field {
            display: flex;
            align-items: center;
            gap: 4px;
            background: #f7f8fc;
            border-radius: 10px;
            border: 2px solid transparent;
            transition: border-color 0.25s, background 0.25s, box-shadow 0.25s;
            padding: 0 6px 0 10px;
            flex-shrink: 0;
        }
        .input-field:focus-within {
            border-color: var(--active-bg);
            background: #fff;
            box-shadow: 0 0 0 4px rgba(255, 126, 95, 0.10);
        }
        .input-field input[type="text"],
        .input-field input[type="number"] {
            border: none;
            outline: none;
            background: transparent;
            font-size: 15px;
            font-weight: 500;
            color: #1a1a2e;
            padding: 8px 2px 8px 0;
            width: 100%;
            min-width: 20px;
            -moz-appearance: textfield;
        }
        .input-field input[type="number"]::-webkit-outer-spin-button,
        .input-field input[type="number"]::-webkit-inner-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }
        .input-field input::placeholder {
            color: #b0b8c4;
            font-weight: 400;
            font-size: 13px;
        }
        .input-field .unit-text {
            font-size: 13px;
            font-weight: 500;
            color: #555;
            white-space: nowrap;
            flex-shrink: 0;
            padding-right: 2px;
        }
        .input-field select {
            padding: 6px 2px 6px 0;
            font-size: 13px;
            font-weight: 500;
            color: #2d3436;
            background: transparent;
            border: none;
            outline: none;
            cursor: pointer;
            flex-shrink: 0;
            min-width: 32px;
            text-align: right;
            appearance: none;
            -webkit-appearance: none;
            background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%23999'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right 0 center;
            padding-right: 14px;
        }
        .input-field select option {
            font-weight: 400;
        }

        /* 各字段宽度 */
        .field-name {
            flex: 2 1 100px;
            min-width: 60px;
        }
        .field-perunit {
            flex: 1 1 80px;
            min-width: 70px;
        }
        .field-energy {
            flex: 1 1 60px;
            min-width: 50px;
        }
        .field-serving {
            flex: 1 1 80px;
            min-width: 70px;
        }
        .field-serving .input-field {
            flex: 1;
        }
        .field-serving .input-field select {
            min-width: 30px;
            padding-right: 12px;
            font-size: 12px;
        }

        /* 按钮 */
        .btn-save-tag,
        .btn-save-record {
            flex-shrink: 0;
            border: none;
            border-radius: 30px;
            padding: 6px 14px;
            font-size: 14px;
            font-weight: 600;
            color: #fff;
            background: var(--active-bg);
            cursor: pointer;
            transition: background 0.2s, transform 0.1s;
            white-space: nowrap;
            box-shadow: 0 2px 8px var(--primary-shadow);
            line-height: 1.4;
        }
        .btn-save-tag:active,
        .btn-save-record:active {
            transform: scale(0.95);
        }
        .btn-save-tag {
            background: rgba(0, 0, 0, 0.08);
            color: #555;
            box-shadow: none;
        }
        .btn-save-tag:active {
            background: rgba(0, 0, 0, 0.15);
        }

        /* ===== 结果区 三列 ===== */
        .result-grid {
            display: grid;
            grid-template-columns: 1fr 1fr 1fr;
            gap: 8px;
            align-items: stretch;
        }
        .result-item {
            background: #f7f8fc;
            border-radius: 12px;
            padding: 10px 6px;
            text-align: center;
            display: flex;
            flex-direction: column;
            justify-content: center;
            min-height: 60px;
        }
        .result-item .rlabel {
            font-size: 12px;
            font-weight: 500;
            color: #888;
        }
        .result-item .rvalue {
            font-size: 24px;
            font-weight: 700;
            color: #1a1a2e;
            line-height: 1.2;
        }
        .result-item .rvalue .runit {
            font-size: 14px;
            font-weight: 400;
            color: #999;
        }
        .result-item.highlight-green .rvalue {
            color: #00b894;
        }
        .result-item.highlight-yellow .rvalue {
            color: #f9ca24;
        }
        .result-item.highlight-red .rvalue {
            color: #e85d3a;
        }
        .result-item.target-item .rvalue {
            color: #2d7dd2;
        }

        /* ===== 差值提示(在结果区下方) ===== */
        .diff-hint {
            margin-top: 10px;
            padding-top: 10px;
            border-top: 1px solid #f0f2f7;
            text-align: center;
            font-size: 14px;
            font-weight: 500;
            color: #1a1a2e;
            min-height: 24px;
        }
        .diff-hint .diff-value {
            font-weight: 700;
        }
        .diff-hint.success .diff-value {
            color: #00b894;
        }
        .diff-hint.warning .diff-value {
            color: #f9ca24;
        }
        .diff-hint.danger .diff-value {
            color: #e85d3a;
        }

        /* ===== 日历卡片 ===== */
        .calendar-wrap {
            padding-bottom: 6px;
        }
        .calendar-nav {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 6px;
        }
        .calendar-nav .cal-month {
            font-size: 16px;
            font-weight: 600;
            color: #1a1a2e;
        }
        .calendar-nav .cal-btn {
            background: #f0f2f7;
            border: none;
            border-radius: 30px;
            padding: 2px 14px;
            font-size: 14px;
            font-weight: 500;
            color: #555;
            cursor: pointer;
            transition: background 0.2s;
        }
        .calendar-nav .cal-btn:active {
            background: #e0e2e7;
        }

        /* 日历网格 - 紧凑长方形 */
        .calendar-grid {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 3px;
            text-align: center;
        }
        .calendar-grid .cal-weekday {
            font-size: 11px;
            font-weight: 500;
            color: #999;
            padding: 2px 0;
        }
        .calendar-grid .cal-day {
            display: flex;
            flex-direction: column;
            justify-content: flex-start;
            align-items: center;
            border-radius: 8px;
            background: #f7f8fc;
            font-weight: 500;
            color: #1a1a2e;
            cursor: pointer;
            transition: background 0.15s, transform 0.1s;
            padding: 4px 2px 3px;
            min-height: 44px;
            border: 2px solid transparent;
            line-height: 1.2;
            position: relative;
        }
        .calendar-grid .cal-day:active {
            transform: scale(0.94);
        }
        .calendar-grid .cal-day .day-number {
            font-size: 15px;
            font-weight: 600;
            line-height: 1.2;
            margin-bottom: 1px;
        }
        .calendar-grid .cal-day .day-kcal {
            font-size: 8px;
            font-weight: 700;
            color: #e85d3a;
            line-height: 1.2;
            max-width: 100%;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .calendar-grid .cal-day .day-weight {
            font-size: 8px;
            font-weight: 700;
            color: #2d7dd2;
            line-height: 1.2;
            max-width: 100%;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .calendar-grid .cal-day.status-green {
            background: #e6f9ed;
            border-color: #4dd4b0;
        }
        .calendar-grid .cal-day.status-yellow {
            background: #fff8e1;
            border-color: #f9ca24;
        }
        .calendar-grid .cal-day.status-red {
            background: #fde8e8;
            border-color: #e85d3a;
        }
        .calendar-grid .cal-day.today {
            border-color: var(--active-bg);
            border-width: 2px;
        }
        .calendar-grid .cal-day.other-month {
            opacity: 0.3;
        }
        .calendar-grid .cal-day.selected {
            box-shadow: 0 0 0 2px var(--active-bg);
        }

        /* ===== 饮食记录卡片(独立) ===== */
        .record-card {
            display: none;
            margin-top: 0;
        }
        .record-card.show {
            display: block;
        }
        .record-card .dd-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 6px;
        }
        .record-card .dd-title {
            font-size: 14px;
            font-weight: 600;
            color: #1a1a2e;
        }
        .record-card .dd-title .dd-total-kcal {
            color: #e85d3a;
            font-weight: 600;
        }
        .record-card .dd-weight {
            font-size: 13px;
            font-weight: 500;
            color: #2d7dd2;
            white-space: nowrap;
        }
        .record-card .dd-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 6px 8px;
            font-size: 14px;
            border-bottom: 1px solid #edf0f5;
            background: #fafbfc;
            border-radius: 6px;
            margin-bottom: 4px;
        }
        .record-card .dd-item:last-child {
            border-bottom: none;
            margin-bottom: 0;
        }
        .record-card .dd-item .dd-name {
            color: #1a1a2e;
            font-weight: 500;
        }
        .record-card .dd-item .dd-kcal {
            font-weight: 500;
            color: #e85d3a;
            margin-left: 8px;
        }
        .record-card .dd-item .dd-del {
            background: none;
            border: none;
            color: #ccc;
            font-size: 16px;
            cursor: pointer;
            padding: 0 4px;
            flex-shrink: 0;
            margin-left: 8px;
            transition: color 0.2s;
        }
        .record-card .dd-item .dd-del:active {
            color: #e85d3a;
        }
        .record-card .dd-empty {
            color: #b0b8c4;
            font-size: 13px;
            text-align: center;
            padding: 16px 0;
        }
        .record-card .meal-group-label {
            font-weight: 600;
            font-size: 13px;
            color: #888;
            margin: 6px 0 4px 4px;
        }

        /* ===== 设置弹窗 ===== */
        .modal-overlay {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.40);
            z-index: 2000;
            display: none;
            align-items: center;
            justify-content: center;
            backdrop-filter: blur(3px);
            padding: 20px;
        }
        .modal-overlay.show {
            display: flex;
        }
        .modal-box {
            background: #fff;
            border-radius: 24px;
            max-width: 420px;
            width: 100%;
            padding: 20px 18px 18px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.20);
            max-height: 90vh;
            overflow-y: auto;
        }
        .modal-box .modal-title {
            font-size: 18px;
            font-weight: 700;
            color: #1a1a2e;
            margin-bottom: 14px;
            text-align: center;
        }
        .settings-grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px 12px;
        }
        .settings-grid .field {
            margin-bottom: 0;
        }
        .settings-grid .field label {
            display: block;
            font-size: 12px;
            font-weight: 500;
            color: #2d3436;
            margin-bottom: 2px;
        }
        .settings-grid .field input,
        .settings-grid .field select {
            width: 100%;
            padding: 7px 10px;
            border: 1px solid #e0e4ec;
            border-radius: 8px;
            font-size: 14px;
            background: #f7f8fc;
            outline: none;
            transition: border-color 0.2s;
        }
        .settings-grid .field input:focus,
        .settings-grid .field select:focus {
            border-color: var(--active-bg);
            background: #fff;
        }
        .settings-grid .field select {
            appearance: auto;
        }
        .settings-target-full {
            grid-column: 1 / -1;
            display: flex;
            gap: 10px;
            margin-top: 2px;
        }
        .settings-target-full button {
            flex: 1;
            padding: 7px 0;
            border: 2px solid #e0e4ec;
            border-radius: 8px;
            background: #f7f8fc;
            font-size: 14px;
            font-weight: 500;
            color: #555;
            cursor: pointer;
            transition: all 0.2s;
        }
        .settings-target-full button.active {
            border-color: var(--active-bg);
            background: #fff5f0;
            color: var(--active-bg);
        }
        .settings-target-full button:active {
            transform: scale(0.96);
        }
        .settings-result {
            grid-column: 1 / -1;
            margin-top: 6px;
            padding: 8px 10px;
            background: #f0f7ff;
            border-radius: 8px;
            font-size: 13px;
            color: #555;
            line-height: 1.6;
        }
        .settings-result strong {
            color: #1a1a2e;
        }
        .modal-actions {
            display: flex;
            gap: 10px;
            margin-top: 14px;
        }
        .modal-actions button {
            flex: 1;
            padding: 9px 0;
            border: none;
            border-radius: 30px;
            font-size: 15px;
            font-weight: 600;
            cursor: pointer;
            transition: background 0.2s;
        }
        .modal-actions .btn-save {
            background: var(--active-bg);
            color: #fff;
        }
        .modal-actions .btn-save:active {
            opacity: 0.8;
        }
        .modal-actions .btn-close {
            background: #f0f2f7;
            color: #555;
        }
        .modal-actions .btn-close:active {
            background: #e0e2e7;
        }

        /* ===== 主题选择弹窗 ===== */
        .theme-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 10px;
            margin: 12px 0;
        }
        .theme-btn {
            padding: 12px 0;
            border: 3px solid #e0e4ec;
            border-radius: 12px;
            cursor: pointer;
            transition: border-color 0.2s, transform 0.1s;
            background: #f7f8fc;
            font-size: 12px;
            font-weight: 500;
            color: #555;
            text-align: center;
        }
        .theme-btn:active {
            transform: scale(0.94);
        }
        .theme-btn.active {
            border-color: var(--active-bg);
            background: #fff5f0;
        }
        .theme-btn .color-preview {
            width: 100%;
            height: 20px;
            border-radius: 6px;
            margin-bottom: 4px;
        }

        /* ===== 备份弹窗 ===== */
        .backup-section {
            margin-bottom: 12px;
        }
        .backup-section-title {
            font-size: 13px;
            font-weight: 600;
            color: #555;
            margin-bottom: 8px;
        }
        .backup-checkboxes {
            display: flex;
            flex-wrap: wrap;
            gap: 6px 12px;
        }
        .backup-checkbox-item {
            display: flex;
            align-items: center;
            gap: 6px;
            font-size: 13px;
            color: #333;
            cursor: pointer;
            flex: 1 1 calc(50% - 6px);
            min-width: 0;
            user-select: none;
        }
        .backup-checkbox-item.backup-full-row {
            flex: 1 1 100%;
        }
        .backup-checkbox-item input[type="checkbox"] {
            width: 16px;
            height: 16px;
            cursor: pointer;
            accent-color: var(--active-bg);
            flex-shrink: 0;
        }
        .backup-checkbox-item span {
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .backup-tip {
            font-size: 12px;
            color: #999;
            text-align: center;
            margin-bottom: 4px;
        }

        /* ===== 体重弹窗 ===== */
        .weight-modal .modal-box {
            max-width: 460px;
        }
        .weight-modal .compact-row {
            display: flex;
            align-items: center;
            gap: 8px;
            flex-wrap: nowrap;
            margin-bottom: 10px;
        }
        .weight-modal .compact-row .field-label {
            font-size: 14px;
            font-weight: 500;
            color: #2d3436;
            white-space: nowrap;
            margin-right: 2px;
        }
        .weight-modal .compact-row input {
            flex: 1;
            min-width: 50px;
            padding: 8px 8px;
            border: 1px solid #e0e4ec;
            border-radius: 8px;
            font-size: 15px;
            background: #f7f8fc;
            outline: none;
            transition: border-color 0.2s;
            width: 100%;
        }
        .weight-modal .compact-row input:focus {
            border-color: var(--active-bg);
            background: #fff;
        }
        .weight-modal .compact-row select {
            flex-shrink: 0;
            padding: 8px 4px 8px 8px;
            border: 1px solid #e0e4ec;
            border-radius: 8px;
            font-size: 14px;
            background: #f7f8fc;
            outline: none;
            cursor: pointer;
            appearance: auto;
        }
        .weight-modal .compact-row select:focus {
            border-color: var(--active-bg);
        }
        .weight-modal .action-row {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-top: 10px;
        }
        .weight-modal .action-row .weight-jin {
            font-size: 14px;
            color: #888;
            flex-shrink: 0;
            min-width: 60px;
            text-align: left;
        }
        .weight-modal .action-row .modal-actions {
            flex: 1;
            margin-top: 0;
            gap: 8px;
        }
        .weight-modal .action-row .modal-actions button {
            padding: 8px 0;
            font-size: 14px;
        }
        .weight-modal .chart-container {
            margin-top: 14px;
            border-top: 1px solid #f0f2f7;
            padding-top: 12px;
        }
        .weight-modal .chart-container .chart-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
            flex-wrap: wrap;
            gap: 4px;
        }
        .weight-modal .chart-container .chart-header .chart-title {
            font-size: 14px;
            font-weight: 600;
            color: #1a1a2e;
        }
        .weight-modal .chart-container .chart-header .period-group {
            display: flex;
            gap: 3px;
            flex-wrap: wrap;
        }
        .weight-modal .chart-container .chart-header .period-group button {
            padding: 2px 8px;
            border: 1px solid #e0e4ec;
            border-radius: 30px;
            background: #f7f8fc;
            font-size: 11px;
            font-weight: 500;
            color: #555;
            cursor: pointer;
            transition: all 0.2s;
        }
        .weight-modal .chart-container .chart-header .period-group button.active {
            background: var(--active-bg);
            color: #fff;
            border-color: var(--active-bg);
        }
        .weight-modal .chart-container .chart-header .period-group button:active {
            transform: scale(0.94);
        }
        .weight-modal .chart-container canvas {
            width: 100%;
            height: 180px;
            background: #fafbfc;
            border-radius: 10px;
            display: block;
        }
        .weight-modal .chart-container .no-data {
            text-align: center;
            padding: 30px 0;
            color: #b0b8c4;
            font-size: 14px;
        }

        /* ===== 删除确认框 ===== */
        .modal-confirm {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #fff;
            border-radius: 20px;
            padding: 20px 24px 18px;
            z-index: 1002;
            max-width: 300px;
            width: 90%;
            box-shadow: 0 16px 60px rgba(0, 0, 0, 0.18);
            display: none;
            text-align: center;
        }
        .modal-confirm.show {
            display: block;
        }
        .modal-confirm .mc-title {
            font-size: 17px;
            font-weight: 600;
            color: #1a1a2e;
            margin-bottom: 4px;
        }
        .modal-confirm .mc-desc {
            font-size: 13px;
            color: #666;
            margin-bottom: 14px;
        }
        .modal-confirm .mc-btns {
            display: flex;
            gap: 10px;
            justify-content: center;
        }
        .modal-confirm .mc-btns button {
            padding: 6px 20px;
            border: none;
            border-radius: 30px;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: background 0.2s;
        }
        .modal-confirm .mc-btns .mc-cancel {
            background: #f0f2f7;
            color: #555;
        }
        .modal-confirm .mc-btns .mc-confirm {
            background: #e85d3a;
            color: #fff;
        }
        .modal-confirm .mc-btns .mc-confirm:active {
            background: #d14a2a;
        }
        .modal-confirm .mc-btns .mc-cancel:active {
            background: #e0e2e7;
        }

        /* ===== Toast 提示 ===== */
        .toast-container {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            z-index: 2000;
            pointer-events: none;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 8px;
        }
        .toast-item {
            background: rgba(0, 0, 0, 0.75);
            color: #fff;
            padding: 10px 20px;
            border-radius: 24px;
            font-size: 14px;
            font-weight: 500;
            max-width: 80vw;
            text-align: center;
            opacity: 0;
            transform: translateY(10px);
            transition: opacity 0.3s ease, transform 0.3s ease;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .toast-item.show {
            opacity: 1;
            transform: translateY(0);
        }
        .toast-item.success {
            background: rgba(0, 184, 148, 0.9);
        }
        .toast-item.error {
            background: rgba(232, 93, 58, 0.9);
        }
        .toast-item.warning {
            background: rgba(249, 202, 36, 0.9);
            color: #333;
        }

        /* ===== 悬浮标签 ===== */
        .tag-fab {
            position: fixed;
            right: 12px;
            bottom: 120px;
            z-index: 1000;
            display: flex;
            flex-direction: column;
            align-items: flex-end;
        }
        .tag-fab .fab-btn {
            width: 52px;
            height: 52px;
            border-radius: 50%;
            background: linear-gradient(135deg, var(--primary-start), var(--primary-end));
            border: none;
            color: #fff;
            font-size: 14px;
            font-weight: 600;
            box-shadow: 0 4px 16px var(--primary-shadow);
            cursor: pointer;
            transition: transform 0.2s, box-shadow 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
            letter-spacing: 0.5px;
        }
        .tag-fab .fab-btn:active {
            transform: scale(0.90);
        }
        .tag-fab .fab-btn .fab-icon {
            font-size: 22px;
            line-height: 1;
        }

        /* ===== 标签面板 ===== */
        .tag-panel {
            position: fixed;
            right: -340px;
            top: 0;
            bottom: 0;
            width: 320px;
            max-width: 85vw;
            background: #fff;
            box-shadow: -4px 0 30px rgba(0, 0, 0, 0.12);
            z-index: 1001;
            transition: right 0.30s cubic-bezier(0.22, 1, 0.36, 1);
            display: flex;
            flex-direction: column;
            border-radius: 20px 0 0 20px;
            overflow: hidden;
        }
        .tag-panel.open {
            right: 0;
        }
        .tag-panel .panel-header {
            padding: 14px 16px 10px;
            border-bottom: 1px solid #f0f2f7;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-shrink: 0;
        }
        .tag-panel .panel-header .ph-title {
            font-size: 17px;
            font-weight: 700;
            color: #1a1a2e;
        }
        .tag-panel .panel-header .ph-close {
            background: none;
            border: none;
            font-size: 22px;
            cursor: pointer;
            color: #999;
            padding: 0 4px;
        }
        .tag-panel .panel-body {
            flex: 1;
            overflow-y: auto;
            padding: 10px 12px 16px;
            -webkit-overflow-scrolling: touch;
            touch-action: pan-y;
        }
        .tag-panel .panel-body .tag-item {
            display: flex;
            align-items: center;
            gap: 6px;
            padding: 10px 12px;
            border-radius: 8px;
            background: #f7f8fc;
            margin-bottom: 4px;
            cursor: pointer;
            transition: background 0.15s, transform 0.1s;
            user-select: none;
            position: relative;
            min-height: 44px;
        }
        .tag-panel .panel-body .tag-item:active {
            transform: scale(0.97);
            background: #eef0f7;
        }
        .tag-panel .panel-body .tag-item.tag-selected {
            background: var(--active-bg);
        }
        .tag-panel .panel-body .tag-item.tag-selected .tag-name {
            color: #fff;
        }
        .tag-panel .panel-body .tag-item.tag-selected span:nth-child(2) {
            color: rgba(255,255,255,0.8) !important;
        }
        .tag-panel .panel-body .tag-item.tag-selected .tag-del {
            color: rgba(255,255,255,0.7);
        }
        .tag-panel .panel-body .tag-item .tag-name {
            flex: 1;
            font-size: 15px;
            font-weight: 500;
            color: #1a1a2e;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            pointer-events: none;
        }
        .tag-panel .panel-body .tag-item .tag-del {
            background: none;
            border: none;
            color: #ccc;
            font-size: 16px;
            cursor: pointer;
            padding: 0 4px;
            flex-shrink: 0;
            pointer-events: auto;
        }
        .tag-panel .panel-body .tag-item .tag-del:active {
            color: #e85d3a;
        }
        .tag-panel .panel-body .tag-add-area {
            margin-top: 10px;
            padding: 10px 12px;
            background: #f7f8fc;
            border-radius: 10px;
            border: 2px dashed #dce0e8;
        }
        .tag-panel .panel-body .tag-add-area .ta-row {
            display: flex;
            gap: 4px;
            margin-bottom: 4px;
            flex-wrap: wrap;
        }
        .tag-panel .panel-body .tag-add-area .ta-row input,
        .tag-panel .panel-body .tag-add-area .ta-row select {
            flex: 1;
            min-width: 50px;
            padding: 6px 8px;
            border: 1px solid #e0e4ec;
            border-radius: 6px;
            font-size: 13px;
            background: #fff;
            outline: none;
            transition: border-color 0.2s;
        }
        .tag-panel .panel-body .tag-add-area .ta-row input:focus,
        .tag-panel .panel-body .tag-add-area .ta-row select:focus {
            border-color: var(--active-bg);
        }
        .tag-panel .panel-body .tag-add-area .ta-row input::placeholder {
            color: #b0b8c4;
        }
        .tag-panel .panel-body .tag-add-area .ta-row .ta-btn {
            padding: 6px 14px;
            border: none;
            border-radius: 6px;
            background: var(--active-bg);
            color: #fff;
            font-weight: 600;
            font-size: 13px;
            cursor: pointer;
            transition: background 0.2s;
        }
        .tag-panel .panel-body .tag-add-area .ta-row .ta-btn:active {
            opacity: 0.8;
        }
        .tag-panel .panel-body .tag-add-area .ta-msg {
            font-size: 12px;
            color: #e85d3a;
            min-height: 18px;
            padding-left: 4px;
        }
        .tag-panel .panel-body .tag-add-area .ta-msg.success {
            color: #00b894;
        }
        .tag-panel .panel-body .tag-count {
            font-size: 12px;
            color: #999;
            text-align: right;
            padding: 2px 4px 6px;
        }
        .overlay-tag {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.25);
            z-index: 1000;
            display: none;
            backdrop-filter: blur(2px);
        }
        .overlay-tag.show {
            display: block;
        }

        /* ===== 暗色模式适配 ===== */
        @media (prefers-color-scheme: dark) {
            body {
                background: #12121a;
                color: #e8e8f0;
            }
            .card {
                background: #1e1e2a;
                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
            }
            .date-meal-row {
                background: transparent;
                border-bottom-color: #2a2a3a;
            }
            .date-meal-row .date-left {
                color: #e8e8f0;
            }
            .date-meal-row .meal-group .meal-btn {
                background: #2a2a38;
                color: #aaa;
            }
            .date-meal-row .meal-group .meal-btn.active {
                background: var(--active-bg);
                color: #fff;
            }
            .input-field {
                background: #2a2a38;
            }
            .input-field input[type="text"],
            .input-field input[type="number"] {
                color: #e8e8f0;
            }
            .input-field input::placeholder {
                color: #6a6a7a;
            }
            .input-field .unit-text {
                color: #aaa;
            }
            .input-field select {
                color: #e8e8f0;
            }
            .input-field select option {
                background: #2a2a38;
                color: #e8e8f0;
            }
            .btn-save-tag {
                color: #ccc;
                background: rgba(255, 255, 255, 0.08);
            }
            .result-item {
                background: #2a2a38;
            }
            .result-item .rlabel {
                color: #7a7a8a;
            }
            .result-item .rvalue {
                color: #e8e8f0;
            }
            .result-item.highlight-green .rvalue {
                color: #4dd4b0;
            }
            .result-item.highlight-yellow .rvalue {
                color: #f9ca24;
            }
            .result-item.highlight-red .rvalue {
                color: #ff6a4a;
            }
            .result-item.target-item .rvalue {
                color: #5b9bd5;
            }
            .diff-hint {
                border-top-color: #2a2a3a;
                color: #e8e8f0;
            }
            .diff-hint.success .diff-value {
                color: #4dd4b0;
            }
            .diff-hint.warning .diff-value {
                color: #f9ca24;
            }
            .diff-hint.danger .diff-value {
                color: #ff6a4a;
            }
            .calendar-grid .cal-day {
                background: #2a2a38;
                color: #e8e8f0;
            }
            .calendar-grid .cal-day .day-kcal {
                color: #ff6a4a;
            }
            .calendar-grid .cal-day.status-green {
                background: #1a2a26;
                border-color: #4dd4b0;
            }
            .calendar-grid .cal-day.status-yellow {
                background: #2a2a1a;
                border-color: #f9ca24;
            }
            .calendar-grid .cal-day.status-red {
                background: #2a1a1a;
                border-color: #ff6a4a;
            }
            .calendar-grid .cal-day.today {
                border-color: var(--active-bg);
            }
            .calendar-grid .cal-day.selected {
                box-shadow: 0 0 0 2px var(--active-bg);
            }
            .calendar-grid .cal-day.other-month {
                opacity: 0.2;
            }
            .calendar-grid .cal-day .day-weight {
                color: #5b9bd5;
            }
            .calendar-nav .cal-btn {
                background: #2a2a38;
                color: #ccc;
            }
            .record-card .dd-item {
                background: #2a2a38;
                border-bottom-color: #3a3a4a;
            }
            .record-card .dd-item .dd-name {
                color: #e8e8f0;
            }
            .record-card .dd-weight {
                color: #5b9bd5;
            }
            .modal-box {
                background: #1e1e2a;
            }
            .modal-box .modal-title {
                color: #e8e8f0;
            }
            .settings-grid .field label {
                color: #d0d0e0;
            }
            .settings-grid .field input,
            .settings-grid .field select {
                background: #2a2a38;
                border-color: #3a3a4a;
                color: #e8e8f0;
            }
            .settings-grid .field input:focus,
            .settings-grid .field select:focus {
                border-color: var(--active-bg);
                background: #1e1e2a;
            }
            .settings-target-full button {
                background: #2a2a38;
                border-color: #3a3a4a;
                color: #aaa;
            }
            .settings-target-full button.active {
                border-color: var(--active-bg);
                background: #2a1e1a;
                color: var(--active-bg);
            }
            .settings-result {
                background: #1a2a3a;
                color: #aaa;
            }
            .settings-result strong {
                color: #e8e8f0;
            }
            .modal-actions .btn-save {
                background: var(--active-bg);
            }
            .modal-actions .btn-close {
                background: #2a2a38;
                color: #ccc;
            }
            .modal-confirm {
                background: #1e1e2a;
            }
            .modal-confirm .mc-title {
                color: #e8e8f0;
            }
            .modal-confirm .mc-desc {
                color: #aaa;
            }
            .modal-confirm .mc-btns .mc-cancel {
                background: #2a2a38;
                color: #ccc;
            }
            .backup-section-title {
                color: #aaa;
            }
            .backup-checkbox-item {
                color: #d0d0e0;
            }
            .backup-tip {
                color: #7a7a8a;
            }
            .tag-panel {
                background: #1e1e2a;
            }
            .tag-panel .panel-header {
                border-bottom-color: #2a2a3a;
            }
            .tag-panel .panel-header .ph-title {
                color: #e8e8f0;
            }
            .tag-panel .panel-body .tag-item {
                background: #2a2a38;
            }
            .tag-panel .panel-body .tag-item:active {
                background: #3a3a4a;
            }
            .tag-panel .panel-body .tag-item.tag-selected {
                background: var(--active-bg);
            }
            .tag-panel .panel-body .tag-item.tag-selected .tag-name {
                color: #fff;
            }
            .tag-panel .panel-body .tag-item .tag-name {
                color: #e8e8f0;
            }
            .tag-panel .panel-body .tag-add-area {
                background: #2a2a38;
                border-color: #3a3a4a;
            }
            .tag-panel .panel-body .tag-add-area .ta-row input,
            .tag-panel .panel-body .tag-add-area .ta-row select {
                background: #1e1e2a;
                border-color: #3a3a4a;
                color: #e8e8f0;
            }
            .tag-panel .panel-body .tag-add-area .ta-row input::placeholder {
                color: #6a6a7a;
            }
            .tag-panel .panel-body .tag-count {
                color: #6a6a7a;
            }
            .overlay-tag {
                background: rgba(0, 0, 0, 0.5);
            }
            .tag-fab .fab-btn {
                background: linear-gradient(135deg, var(--primary-start), var(--primary-end));
            }
            .weight-modal .chart-container canvas {
                background: #1e1e2a;
            }
            .weight-modal .chart-container .chart-header .period-group button {
                background: #2a2a38;
                border-color: #3a3a4a;
                color: #aaa;
            }
            .weight-modal .chart-container .chart-header .period-group button.active {
                background: var(--active-bg);
                border-color: var(--active-bg);
                color: #fff;
            }
            .weight-modal .compact-row select {
                background: #2a2a38;
                border-color: #3a3a4a;
                color: #e8e8f0;
            }
            .weight-modal .compact-row input {
                background: #2a2a38;
                border-color: #3a3a4a;
                color: #e8e8f0;
            }
            .weight-modal .compact-row input:focus {
                border-color: var(--active-bg);
                background: #1e1e2a;
            }
        }

        /* ===== 响应式 ===== */
        @media (max-width: 420px) {
            body {
                padding: 6px 6px 80px;
            }
            .card {
                padding: 10px 10px;
                border-radius: 14px;
            }
            .top-bar {
                padding: 6px 12px;
                min-height: 42px;
                border-radius: 14px;
            }
            .top-bar .brand .icon {
                font-size: 22px;
            }
            .top-bar .brand .title {
                font-size: 16px;
            }
            .top-bar .actions button {
                font-size: 13px;
                padding: 3px 8px;
            }
            .date-meal-row .date-left {
                font-size: 13px;
            }
            .date-meal-row .meal-group .meal-btn {
                font-size: 11px;
                padding: 2px 8px;
            }
            .input-row-wrap {
                gap: 6px;
                margin-bottom: 6px;
                flex-wrap: wrap;
            }
            .input-field {
                padding: 0 4px 0 8px;
                border-radius: 8px;
            }
            .input-field input[type="text"],
            .input-field input[type="number"] {
                font-size: 14px;
                padding: 6px 2px 6px 0;
            }
            .input-field .unit-text {
                font-size: 12px;
            }
            .input-field select {
                font-size: 12px;
                padding: 4px 2px 4px 0;
                min-width: 24px;
                padding-right: 12px;
            }
            .field-name {
                flex: 2 1 70px;
                min-width: 50px;
            }
            .field-perunit {
                flex: 1 1 65px;
                min-width: 55px;
            }
            .field-energy {
                flex: 1 1 50px;
                min-width: 45px;
            }
            .field-serving {
                flex: 1 1 70px;
                min-width: 60px;
            }
            .btn-save-tag,
            .btn-save-record {
                font-size: 12px;
                padding: 4px 10px;
            }
            .result-grid {
                grid-template-columns: 1fr 1fr 1fr;
                gap: 4px;
            }
            .result-item {
                padding: 6px 4px;
                min-height: 48px;
            }
            .result-item .rvalue {
                font-size: 20px;
            }
            .diff-hint {
                font-size: 13px;
            }
            .calendar-grid .cal-day {
                min-height: 36px;
                padding: 3px 2px 2px;
            }
            .calendar-grid .cal-day .day-number {
                font-size: 13px;
            }
            .calendar-grid .cal-day .day-kcal {
                font-size: 7px;
            }
            .calendar-grid .cal-day .day-weight {
                font-size: 7px;
            }
            .record-card .dd-item {
                font-size: 13px;
                padding: 4px 6px;
            }
            .tag-panel {
                width: 270px;
                max-width: 80vw;
            }
            .weight-modal .modal-box {
                padding: 16px 12px;
            }
            .weight-modal .compact-row {
                flex-wrap: nowrap;
                gap: 4px;
            }
            .weight-modal .compact-row input {
                min-width: 40px;
                padding: 6px 4px;
                font-size: 14px;
            }
            .weight-modal .compact-row select {
                font-size: 12px;
                padding: 6px 4px;
            }
            .weight-modal .chart-container canvas {
                height: 130px;
            }
            .weight-modal .action-row {
                flex-wrap: nowrap;
                gap: 6px;
            }
            .weight-modal .action-row .weight-jin {
                font-size: 12px;
                min-width: 40px;
            }
            .weight-modal .action-row .modal-actions button {
                font-size: 13px;
                padding: 6px 0;
            }
            .settings-grid {
                grid-template-columns: 1fr 1fr;
                gap: 6px 8px;
            }
            .settings-grid .field label {
                font-size: 11px;
            }
            .settings-grid .field input,
            .settings-grid .field select {
                font-size: 13px;
                padding: 5px 8px;
            }
            .settings-target-full button {
                font-size: 13px;
                padding: 5px 0;
            }
            .settings-result {
                font-size: 12px;
                padding: 6px 8px;
            }
            .modal-box {
                padding: 16px 14px;
            }
            .modal-box .modal-title {
                font-size: 16px;
                margin-bottom: 10px;
            }
            .theme-grid {
                grid-template-columns: repeat(3, 1fr);
                gap: 8px;
            }
        }
        @media (min-width: 769px) {
            body {
                padding: 20px 20px 120px;
                display: flex;
                justify-content: center;
            }
            .app {
                max-width: 540px;
            }
            .tag-fab {
                right: 30px;
                bottom: 140px;
            }
            .tag-panel {
                width: 360px;
                max-width: 30vw;
                border-radius: 24px 0 0 24px;
            }
            .result-item .rvalue {
                font-size: 28px;
            }
            .calendar-grid .cal-day {
                min-height: 48px;
                padding: 4px 2px 3px;
            }
            .calendar-grid .cal-day .day-number {
                font-size: 16px;
            }
            .calendar-grid .cal-day .day-kcal {
                font-size: 9px;
            }
            .calendar-grid .cal-day .day-weight {
                font-size: 9px;
            }
            .weight-modal .chart-container canvas {
                height: 200px;
            }
            .input-field input[type="text"],
            .input-field input[type="number"] {
                font-size: 16px;
                padding: 10px 4px 10px 0;
            }
        }
    </style>
</head>
<body>

    <div class="app" id="app">

        <!-- ===== 顶部栏 ===== -->
        <div class="top-bar" id="topBar">
            <div class="brand">
                <span class="title">热量计算/体重记录</span>
            </div>
            <div class="actions">
                <button id="weightToggle">体重</button>
                <button id="backupToggle">备份</button>
                <button id="settingsToggle">设置</button>
                <button id="themeToggle">主题</button>
            </div>
        </div>

        <!-- ===== 卡片1:日期+餐次+输入 ===== -->
        <div class="card">
            <!-- 日期+餐次 -->
            <div class="date-meal-row">
                <div class="date-left">
                    <span class="date-icon">&#128197;</span>
                    <span id="dateDisplay">--</span>
                </div>
                <div class="meal-group" id="mealGroup">
                    <button class="meal-btn" data-meal="早餐">早餐</button>
                    <button class="meal-btn" data-meal="中餐">中餐</button>
                    <button class="meal-btn" data-meal="晚餐">晚餐</button>
                    <button class="meal-btn" data-meal="加餐">加餐</button>
                </div>
            </div>

            <!-- 输入行1:食物名称 + 每份量 + 存为标签 -->
            <div class="input-row-wrap">
                <div class="field-name">
                    <div class="input-field">
                        <input type="text" id="foodName" placeholder="食物名称" />
                    </div>
                </div>
                <div class="field-perunit">
                    <div class="input-field">
                        <input type="number" id="defaultWeight" value="100" min="0.1" step="1" />
                        <select id="defaultUnit">
                            <option value="g">g</option>
                            <option value="ml">ml</option>
                        </select>
                    </div>
                </div>
                <button class="btn-save-tag" id="saveTagQuick">存为标签</button>
            </div>

            <!-- 输入行2:能量 + 食用量 + 存储记录 -->
            <div class="input-row-wrap">
                <div class="field-energy">
                    <div class="input-field">
                        <input type="number" id="energy" placeholder="能量" min="0" step="1" />
                        <span class="unit-text">kJ</span>
                    </div>
                </div>
                <div class="field-serving">
                    <div class="input-field">
                        <input type="number" id="serving" placeholder="食用量" min="0" step="1" />
                        <select id="servingUnit">
                            <option value="g">g</option>
                            <option value="kg">kg</option>
                            <option value="ml">ml</option>
                        </select>
                    </div>
                </div>
                <button class="btn-save-record" id="saveRecordBtn">存储记录</button>
            </div>
        </div>

        <!-- ===== 卡片2:结果区 三列 + 差值提示 ===== -->
        <div class="card">
            <div class="result-grid" id="resultGrid">
                <div class="result-item" id="currentMealItem">
                    <div class="rlabel">本餐热量</div>
                    <div class="rvalue" id="currentMealKcal"><span class="placeholder">--</span><span class="runit">千卡</span></div>
                </div>
                <div class="result-item" id="todayTotalItem">
                    <div class="rlabel">今日总热量</div>
                    <div class="rvalue" id="todayTotalKcal"><span class="placeholder">0</span><span class="runit">千卡</span></div>
                </div>
                <div class="result-item target-item" id="targetItem">
                    <div class="rlabel">目标热量</div>
                    <div class="rvalue" id="targetKcal"><span class="placeholder">--</span><span class="runit">千卡</span></div>
                </div>
            </div>
            <!-- 差值提示放在结果区下方 -->
            <div class="diff-hint" id="diffHint"></div>
        </div>

        <!-- ===== 卡片3:日历 ===== -->
        <div class="card calendar-wrap">
            <div class="calendar-nav">
                <button class="cal-btn" id="calPrev">&#8249;</button>
                <span class="cal-month" id="calMonth">--</span>
                <button class="cal-btn" id="calNext">&#8250;</button>
            </div>
            <div class="calendar-grid" id="calendarGrid"></div>
        </div>

        <!-- ===== 卡片4:饮食记录(点击日期后显示) ===== -->
        <div class="card record-card" id="recordCard">
            <div class="dd-header">
                <div class="dd-title" id="ddTitle">&#128203; 详细记录</div>
                <div class="dd-weight" id="ddWeight"></div>
            </div>
            <div id="ddList"></div>
        </div>

    </div>

    <!-- ===== 设置弹窗 ===== -->
    <div class="modal-overlay" id="settingsModal">
        <div class="modal-box">
            <div class="modal-title">&#9881;&#65039; 个人基础信息</div>
            <div class="settings-grid">
                <div class="field">
                    <label>年龄</label>
                    <input type="number" id="userAge" placeholder="岁" min="10" max="120" />
                </div>
                <div class="field">
                    <label>性别</label>
                    <select id="userGenderSelect">
                        <option value="male">男</option>
                        <option value="female">女</option>
                    </select>
                </div>
                <div class="field">
                    <label>身高 (cm)</label>
                    <input type="number" id="userHeight" placeholder="cm" min="100" max="250" step="0.1" />
                </div>
                <div class="field">
                    <label>运动量</label>
                    <select id="userActivity">
                        <option value="1.2">久坐</option>
                        <option value="1.375">轻度</option>
                        <option value="1.55">中度</option>
                        <option value="1.725">重度</option>
                        <option value="1.9">极重度</option>
                    </select>
                </div>
                <div class="field">
                    <label>体重 (kg)</label>
                    <input type="number" id="userWeight" placeholder="kg" min="20" max="300" step="0.1" />
                </div>
                <div class="field" style="display:flex;align-items:flex-end;">
                    <div style="width:100%;">
                        <label>目标</label>
                        <div class="settings-target-full" id="targetGroup">
                            <button data-target="maintain">维持</button>
                            <button data-target="lose" class="active">减肥</button>
                        </div>
                    </div>
                </div>
                <div class="settings-result" id="calcInfo">
                    <strong>估算</strong> · BMR: <span id="bmrDisplay">--</span> 千卡  |  TDEE: <span id="tdeeDisplay">--</span> 千卡<br />
                    <span id="targetSuggestion">建议每日摄入 <strong>--</strong> 千卡</span>
                </div>
            </div>
            <div class="modal-actions">
                <button class="btn-close" id="settingsClose">取消</button>
                <button class="btn-save" id="settingsSave">保存</button>
            </div>
        </div>
    </div>

    <!-- ===== 主题选择弹窗 ===== -->
    <div class="modal-overlay" id="themeModal">
        <div class="modal-box">
            <div class="modal-title">&#127912; 选择主题色</div>
            <div class="theme-grid" id="themeGrid"></div>
            <div class="modal-actions">
                <button class="btn-close" id="themeClose">关闭</button>
            </div>
        </div>
    </div>

    <!-- ===== 体重弹窗 ===== -->
    <div class="modal-overlay weight-modal" id="weightModal">
        <div class="modal-box">
            <div class="modal-title">&#9878;&#65039; 体重记录</div>
            <div class="compact-row">
                <span class="field-label">体重</span>
                <input type="number" id="weightInput" placeholder="kg" min="20" max="300" step="0.1" />
                <span style="font-size:14px;color:#888;margin:0 2px;">kg</span>
                <span style="font-size:14px;color:#888;margin:0 2px;">·</span>
                <select id="weightTime">
                    <option value="自动">自动</option>
                    <option value="早晨">早晨</option>
                    <option value="中午">中午</option>
                    <option value="晚上">晚上</option>
                    <option value="其他">其他</option>
                </select>
            </div>
            <div class="action-row">
                <span class="weight-jin" id="weightJinDisplay"></span>
                <div class="modal-actions">
                    <button class="btn-close" id="weightClose">取消</button>
                    <button class="btn-save" id="weightSave">保存</button>
                </div>
            </div>
            <div class="chart-container" id="weightChartContainer">
                <div class="chart-header">
                    <span class="chart-title">&#128201; 体重趋势</span>
                    <div class="period-group" id="weightPeriodGroup">
                        <button data-period="7" class="active">7天</button>
                        <button data-period="15">半月</button>
                        <button data-period="30">1月</button>
                        <button data-period="90">3月</button>
                        <button data-period="180">半年</button>
                        <button data-period="365">1年</button>
                    </div>
                </div>
                <canvas id="weightChartCanvas"></canvas>
                <div class="no-data" id="weightNoData" style="display:none;">暂无体重数据</div>
            </div>
        </div>
    </div>

    <!-- ===== 备份弹窗 ===== -->
    <div class="modal-overlay" id="backupModal">
        <div class="modal-box">
            <div class="modal-title">&#128190; 数据备份</div>
            <div class="backup-section">
                <div class="backup-section-title">选择导出内容</div>
                <div class="backup-checkboxes">
                    <label class="backup-checkbox-item">
                        <input type="checkbox" id="backupPersonal" checked />
                        <span>个人基本信息</span>
                    </label>
                    <label class="backup-checkbox-item">
                        <input type="checkbox" id="backupTheme" checked />
                        <span>主题配置</span>
                    </label>
                    <label class="backup-checkbox-item">
                        <input type="checkbox" id="backupTags" checked />
                        <span>自定义标签</span>
                    </label>
                    <label class="backup-checkbox-item backup-full-row">
                        <input type="checkbox" id="backupRecords" checked />
                        <span>全部历史记录(饮食+热量)</span>
                    </label>
                    <label class="backup-checkbox-item backup-full-row">
                        <input type="checkbox" id="backupWeight" checked />
                        <span>体重记录</span>
                    </label>
                </div>
            </div>
            <div class="backup-tip">导入数据将覆盖当前对应分类的数据</div>
            <input type="file" id="backupFileInput" accept=".json,.txt" style="display:none;" />
            <div class="modal-actions">
                <button class="btn-close" id="backupImportBtn">导入数据</button>
                <button class="btn-save" id="backupExportBtn">导出数据</button>
            </div>
        </div>
    </div>

    <!-- ===== 遮罩 (标签) ===== -->
    <div class="overlay-tag" id="overlayTag"></div>

    <!-- ===== 删除确认框 ===== -->
    <div class="modal-confirm" id="confirmModal">
        <div class="mc-title">&#9888;&#65039; 确认删除</div>
        <div class="mc-desc" id="confirmDesc">确定要删除该记录吗?</div>
        <div class="mc-btns">
            <button class="mc-cancel" id="confirmCancel">取消</button>
            <button class="mc-confirm" id="confirmOk">删除</button>
        </div>
    </div>

    <!-- ===== Toast 提示 ===== -->
    <div class="toast-container" id="toastContainer"></div>

    <!-- ===== 悬浮标签 ===== -->
    <div class="tag-fab" id="tagFab">
        <button class="fab-btn" id="fabToggle"><span class="fab-icon">&#127991;&#65039;</span></button>
    </div>

    <!-- ===== 标签面板 ===== -->
    <div class="tag-panel" id="tagPanel">
        <div class="panel-header">
            <span class="ph-title">&#127991;&#65039; 常用标签</span>
            <button class="ph-close" id="panelClose">&#10005;</button>
        </div>
        <div class="panel-body" id="panelBody">
            <div class="tag-count" id="tagCount">0 / 50</div>
            <div id="tagList"></div>
            <div class="tag-add-area">
                <div class="ta-row">
                    <input type="text" id="tagNameInput" placeholder="名称" maxlength="20" />
                    <input type="number" id="tagWeightInput" placeholder="份量" min="0.1" step="1" />
                    <select id="tagUnitInput">
                        <option value="g">g</option>
                        <option value="ml">ml</option>
                    </select>
                    <input type="number" id="tagEnergyInput" placeholder="能量(kJ)" min="0" step="1" />
                </div>
                <div class="ta-row" style="justify-content:flex-end;">
                    <button class="ta-btn" id="tagSaveBtn">保存标签</button>
                </div>
                <div class="ta-msg" id="tagMsg"></div>
            </div>
        </div>
    </div>

    <script>
        (function() {
            'use strict';

            // ============================================================
            //  数据层
            // ============================================================
            const STORAGE_KEY = 'food_cal_data_v12';
            const SETTINGS_KEY = 'user_settings_v5';
            const THEME_KEY = 'user_theme_v2';
            const INPUT_CACHE_KEY = 'food_input_cache_v1';

            const DEFAULT_TAGS = [
                { name: '鸡胸肉', defaultWeight: 100, unit: 'g', energy: 550 },
                { name: '牛肉(瘦)', defaultWeight: 100, unit: 'g', energy: 480 },
                { name: '猪里脊', defaultWeight: 100, unit: 'g', energy: 520 },
                { name: '三文鱼', defaultWeight: 100, unit: 'g', energy: 600 },
                { name: '鳕鱼', defaultWeight: 100, unit: 'g', energy: 370 },
                { name: '鸡蛋(全蛋)', defaultWeight: 50, unit: 'g', energy: 315 },
                { name: '蛋白', defaultWeight: 50, unit: 'g', energy: 85 },
                { name: '脱脂牛奶', defaultWeight: 100, unit: 'ml', energy: 140 },
                { name: '纯牛奶', defaultWeight: 100, unit: 'ml', energy: 270 },
                { name: '希腊酸奶', defaultWeight: 100, unit: 'g', energy: 320 },
                { name: '燕麦片', defaultWeight: 100, unit: 'g', energy: 1500 },
                { name: '全麦面包', defaultWeight: 100, unit: 'g', energy: 1000 },
                { name: '糙米', defaultWeight: 100, unit: 'g', energy: 480 },
                { name: '藜麦', defaultWeight: 100, unit: 'g', energy: 500 },
                { name: '红薯', defaultWeight: 100, unit: 'g', energy: 360 },
                { name: '玉米', defaultWeight: 100, unit: 'g', energy: 350 },
                { name: '西兰花', defaultWeight: 100, unit: 'g', energy: 140 },
                { name: '菠菜', defaultWeight: 100, unit: 'g', energy: 100 },
                { name: '生菜', defaultWeight: 100, unit: 'g', energy: 60 },
                { name: '番茄', defaultWeight: 100, unit: 'g', energy: 80 },
                { name: '黄瓜', defaultWeight: 100, unit: 'g', energy: 65 },
                { name: '胡萝卜', defaultWeight: 100, unit: 'g', energy: 110 },
                { name: '苹果', defaultWeight: 100, unit: 'g', energy: 218 },
                { name: '香蕉', defaultWeight: 100, unit: 'g', energy: 381 },
                { name: '蓝莓', defaultWeight: 100, unit: 'g', energy: 240 },
                { name: '草莓', defaultWeight: 100, unit: 'g', energy: 130 },
                { name: '牛油果', defaultWeight: 100, unit: 'g', energy: 670 },
                { name: '豆腐', defaultWeight: 100, unit: 'g', energy: 320 },
                { name: '豆浆(无糖)', defaultWeight: 100, unit: 'ml', energy: 120 },
                { name: '奶酪(低脂)', defaultWeight: 100, unit: 'g', energy: 500 },
            ];

            function getDefaultData() {
                const now = Date.now();
                return {
                    tags: DEFAULT_TAGS.map((t, i) => ({ ...t, id: 'tag_' + now + '_' + i, lastUsed: now - i })),
                    records: {},
                    weightRecords: {}
                };
            }

            let data = loadData();
            let userSettings = loadSettings();
            let currentTheme = loadTheme();

            function loadData() {
                try {
                    const raw = localStorage.getItem(STORAGE_KEY);
                    if (raw) {
                        const parsed = JSON.parse(raw);
                        if (parsed && typeof parsed === 'object' && parsed.tags && parsed.records) {
                            if (!parsed.weightRecords) parsed.weightRecords = {};
                            const now = Date.now();
                            parsed.tags.forEach((t, i) => {
                                if (!t.unit) t.unit = 'g';
                                if (!t.lastUsed) t.lastUsed = now - i;
                            });
                            return parsed;
                        }
                    }
                } catch (_) {}
                return getDefaultData();
            }

            function saveData() {
                try {
                    localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
                } catch (_) {}
            }

            function loadSettings() {
                try {
                    const raw = localStorage.getItem(SETTINGS_KEY);
                    if (raw) {
                        const s = JSON.parse(raw);
                        if (s && typeof s === 'object') return s;
                    }
                } catch (_) {}
                return {
                    age: '',
                    gender: 'male',
                    height: '',
                    weight: '',
                    activity: 1.2,
                    target: 'lose'
                };
            }

            function saveSettings(settings) {
                userSettings = settings;
                try {
                    localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
                } catch (_) {}
            }

            function loadTheme() {
                try {
                    const t = localStorage.getItem(THEME_KEY);
                    if (t) return t;
                } catch (_) {}
                return 'theme-1';
            }

            function saveTheme(theme) {
                currentTheme = theme;
                try {
                    localStorage.setItem(THEME_KEY, theme);
                } catch (_) {}
            }

            // ============================================================
            //  食品输入缓存
            // ============================================================
            function loadInputCache() {
                try {
                    const raw = localStorage.getItem(INPUT_CACHE_KEY);
                    if (raw) {
                        const c = JSON.parse(raw);
                        if (c && typeof c === 'object') return c;
                    }
                } catch (_) {}
                return null;
            }

            function saveInputCache() {
                const cache = {
                    foodName: document.getElementById('foodName').value,
                    defaultWeight: document.getElementById('defaultWeight').value,
                    defaultUnit: document.getElementById('defaultUnit').value,
                    energy: document.getElementById('energy').value,
                    serving: document.getElementById('serving').value,
                    servingUnit: document.getElementById('servingUnit').value
                };
                try {
                    localStorage.setItem(INPUT_CACHE_KEY, JSON.stringify(cache));
                } catch (_) {}
            }

            function applyInputCache() {
                const cache = loadInputCache();
                if (cache) {
                    if (cache.foodName !== undefined) document.getElementById('foodName').value = cache.foodName;
                    if (cache.defaultWeight !== undefined) document.getElementById('defaultWeight').value = cache.defaultWeight;
                    if (cache.defaultUnit !== undefined) document.getElementById('defaultUnit').value = cache.defaultUnit;
                    if (cache.energy !== undefined) document.getElementById('energy').value = cache.energy;
                    if (cache.serving !== undefined) document.getElementById('serving').value = cache.serving;
                    if (cache.servingUnit !== undefined) document.getElementById('servingUnit').value = cache.servingUnit;
                }
            }

            // ============================================================
            //  数据导出/导入
            // ============================================================
            function exportBackupData(options) {
                const exportData = {
                    version: 1,
                    exportTime: new Date().toISOString(),
                    app: '热量计算+体重记录'
                };
                if (options.personal) {
                    exportData.personal = userSettings;
                }
                if (options.theme) {
                    exportData.theme = currentTheme;
                }
                if (options.tags) {
                    exportData.tags = data.tags;
                }
                if (options.records) {
                    exportData.records = data.records;
                }
                if (options.weight) {
                    exportData.weightRecords = data.weightRecords;
                }
                return exportData;
            }

            function formatBackupFilename() {
                const d = new Date();
                const y = d.getFullYear();
                const m = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                const h = String(d.getHours()).padStart(2, '0');
                const min = String(d.getMinutes()).padStart(2, '0');
                const s = String(d.getSeconds()).padStart(2, '0');
                return '热量计算加体重记录_' + y + m + day + '_' + h + min + s + '.json';
            }

            function downloadFile(content, filename) {
                const blob = new Blob([content], { type: 'application/json;charset=utf-8' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = filename;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                setTimeout(() => { URL.revokeObjectURL(url); }, 100);
            }

            function doExport() {
                const options = {
                    personal: document.getElementById('backupPersonal').checked,
                    theme: document.getElementById('backupTheme').checked,
                    tags: document.getElementById('backupTags').checked,
                    records: document.getElementById('backupRecords').checked,
                    weight: document.getElementById('backupWeight').checked
                };
                const hasAny = options.personal || options.theme || options.tags || options.records || options.weight;
                if (!hasAny) {
                    alert('请至少选择一项导出内容');
                    return;
                }
                const exportData = exportBackupData(options);
                const content = JSON.stringify(exportData, null, 2);
                const filename = formatBackupFilename();
                downloadFile(content, filename);
            }

            function importBackupData(parsedData) {
                let importedCount = 0;
                if (parsedData.personal && typeof parsedData.personal === 'object') {
                    saveSettings(parsedData.personal);
                    importedCount++;
                }
                if (parsedData.theme && typeof parsedData.theme === 'string') {
                    applyTheme(parsedData.theme);
                    importedCount++;
                }
                if (parsedData.tags && Array.isArray(parsedData.tags)) {
                    data.tags = parsedData.tags;
                    saveData();
                    importedCount++;
                }
                if (parsedData.records && typeof parsedData.records === 'object') {
                    data.records = parsedData.records;
                    saveData();
                    importedCount++;
                }
                if (parsedData.weightRecords && typeof parsedData.weightRecords === 'object') {
                    data.weightRecords = parsedData.weightRecords;
                    saveData();
                    importedCount++;
                }
                return importedCount;
            }

            function handleImportFile(file) {
                const reader = new FileReader();
                reader.onload = function(e) {
                    try {
                        const content = e.target.result;
                        const parsed = JSON.parse(content);
                        if (!parsed || typeof parsed !== 'object') {
                            alert('文件格式不正确');
                            return;
                        }
                        const count = importBackupData(parsed);
                        if (count === 0) {
                            alert('未找到可导入的数据');
                            return;
                        }
                        alert('&#9989; 导入成功,共导入 ' + count + ' 类数据');
                        closeBackupModal();
                        renderTags();
                        renderCalendar();
                        computePreview();
                        if (selectedDateStr) {
                            renderRecordCard(selectedDateStr);
                        }
                    } catch (err) {
                        alert('导入失败:文件格式错误');
                    }
                };
                reader.onerror = function() {
                    alert('读取文件失败');
                };
                reader.readAsText(file, 'UTF-8');
            }

            // ============================================================
            //  主题
            // ============================================================
            const THEMES = [
                { id: 'theme-1', name: '橙红', colors: ['#ff7e5f', '#feb47b'] },
                { id: 'theme-2', name: '翠绿', colors: ['#11998e', '#38ef7d'] },
                { id: 'theme-3', name: '天蓝', colors: ['#2193b0', '#6dd5ed'] },
                { id: 'theme-4', name: '紫罗兰', colors: ['#8E2DE2', '#4A00E0'] },
                { id: 'theme-5', name: '粉红', colors: ['#f093fb', '#f5576c'] },
                { id: 'theme-6', name: '青草', colors: ['#a8e063', '#56ab2f'] },
                { id: 'theme-7', name: '日落', colors: ['#f2994a', '#f2c94c'] },
                { id: 'theme-8', name: '冰川', colors: ['#00c6fb', '#005bea'] },
                { id: 'theme-9', name: '玫瑰', colors: ['#cb2d3e', '#ef473a'] },
                { id: 'theme-10', name: '薄荷', colors: ['#00b09b', '#96c93d'] },
                { id: 'theme-11', name: '星空', colors: ['#1a2a6c', '#b21f1f'] },
                { id: 'theme-12', name: '碳灰', colors: ['#2c3e50', '#3498db'] },
            ];

            function applyTheme(themeId) {
                const root = document.documentElement;
                root.className = themeId;
                const theme = THEMES.find(t => t.id === themeId);
                if (theme) {
                    const [start, end] = theme.colors;
                    root.style.setProperty('--primary-start', start);
                    root.style.setProperty('--primary-end', end);
                    root.style.setProperty('--active-bg', start);
                    root.style.setProperty('--primary-shadow', start + '40');
                }
                saveTheme(themeId);
            }

            // ============================================================
            //  工具
            // ============================================================
            function formatDate(d) {
                const m = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                const wd = ['日', '一', '二', '三', '四', '五', '六'][d.getDay()];
                return m + '月' + day + '日 周' + wd;
            }

            function formatDateShort(d) {
                const m = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                return m + '/' + day;
            }

            function getTodayStr() {
                const d = new Date();
                const y = d.getFullYear();
                const m = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                return y + '-' + m + '-' + day;
            }

            function parseDate(str) {
                const parts = str.split('-').map(Number);
                return new Date(parts[0], parts[1] - 1, parts[2]);
            }

            function getAutoMeal() {
                const h = new Date().getHours();
                if (h >= 5 && h < 9) return '早餐';
                if (h >= 11 && h < 14) return '中餐';
                if (h >= 17 && h < 21) return '晚餐';
                return '加餐';
            }

            function getNumberValue(el) {
                const v = el.value.trim();
                if (v === '') return NaN;
                const n = parseFloat(v);
                return isNaN(n) ? NaN : n;
            }

            function generateId() {
                return 'id_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6);
            }

            function escapeHtml(str) {
                const div = document.createElement('div');
                div.textContent = str;
                return div.innerHTML;
            }

            // ============================================================
            //  数据操作
            // ============================================================
            function getRecordsForDate(dateStr) {
                return data.records[dateStr] || [];
            }

            function getTotalKcalForDate(dateStr) {
                const recs = getRecordsForDate(dateStr);
                let total = 0;
                recs.forEach(r => { total += r.totalKcal || 0; });
                return total;
            }

            function getWeightForDate(dateStr) {
                return data.weightRecords[dateStr] !== undefined ? data.weightRecords[dateStr] : null;
            }

            function setWeightForDate(dateStr, weightKg) {
                data.weightRecords[dateStr] = weightKg;
                saveData();
            }

            function getWeightRecordsInRange(startDate, endDate) {
                const records = data.weightRecords || {};
                const result = [];
                const start = new Date(startDate);
                const end = new Date(endDate);
                for (const [dateStr, weight] of Object.entries(records)) {
                    const d = parseDate(dateStr);
                    if (d >= start && d <= end) {
                        result.push({ date: dateStr, weight: weight });
                    }
                }
                result.sort((a, b) => a.date.localeCompare(b.date));
                return result;
            }

            function getRecentRange(days) {
                const today = new Date();
                const end = new Date(today);
                const start = new Date(today);
                start.setDate(start.getDate() - days + 1);
                return { start: start, end: end };
            }

            // ============================================================
            //  设置相关
            // ============================================================
            function getTargetCalories() {
                const age = parseInt(userSettings.age);
                const gender = userSettings.gender;
                const height = parseFloat(userSettings.height);
                const weight = parseFloat(userSettings.weight);
                const activity = parseFloat(userSettings.activity);
                const target = userSettings.target || 'lose';

                if (isNaN(age) || isNaN(height) || isNaN(weight) || age <= 0 || height <= 0 || weight <= 0) {
                    return { tdee: null, targetKcal: null };
                }

                let bmr;
                if (gender === 'male') {
                    bmr = 10 * weight + 6.25 * height - 5 * age + 5;
                } else {
                    bmr = 10 * weight + 6.25 * height - 5 * age - 161;
                }
                const tdee = bmr * activity;

                let targetKcal;
                if (target === 'maintain') {
                    targetKcal = tdee;
                } else {
                    targetKcal = tdee - 400;
                    if (targetKcal < 1000) targetKcal = 1000;
                }
                return { tdee, targetKcal };
            }

            function getTargetDisplay() {
                const result = getTargetCalories();
                if (result.targetKcal === null) return null;
                return Math.round(result.targetKcal);
            }

            // ============================================================
            //  渲染:日期+餐次
            // ============================================================
            function updateDateDisplay() {
                const d = new Date();
                document.getElementById('dateDisplay').textContent = formatDate(d);
            }

            // ============================================================
            //  渲染:结果区 + 差值提示
            // ============================================================
            function updateResults(mealKcal, todayTotal, target) {
                const mealEl = document.getElementById('currentMealKcal');
                const todayEl = document.getElementById('todayTotalKcal');
                const targetEl = document.getElementById('targetKcal');
                const diffEl = document.getElementById('diffHint');

                if (mealKcal !== null && !isNaN(mealKcal) && mealKcal > 0) {
                    mealEl.innerHTML = mealKcal.toFixed(1) + '<span class="runit"> 千卡</span>';
                } else {
                    mealEl.innerHTML = '<span class="placeholder">--</span><span class="runit"> 千卡</span>';
                }

                const todayVal = todayTotal || 0;
                todayEl.innerHTML = todayVal.toFixed(1) + '<span class="runit"> 千卡</span>';

                const todayItem = document.getElementById('todayTotalItem');
                todayItem.className = 'result-item';
                if (target !== null && target > 0 && todayVal > 0) {
                    const ratio = todayVal / target;
                    if (ratio > 1.0) {
                        todayItem.classList.add('highlight-red');
                    } else if (ratio >= 0.9) {
                        todayItem.classList.add('highlight-yellow');
                    } else {
                        todayItem.classList.add('highlight-green');
                    }
                } else if (todayVal > 0) {
                    todayItem.classList.add('highlight-green');
                }

                if (target !== null) {
                    targetEl.innerHTML = target + '<span class="runit"> 千卡</span>';
                } else {
                    targetEl.innerHTML = '<span class="placeholder">--</span><span class="runit"> 千卡</span>';
                }

                // 差值提示
                if (target === null || target <= 0) {
                    diffEl.textContent = '请先在「设置」中填写个人信息以计算目标热量';
                    diffEl.className = 'diff-hint';
                    return;
                }
                const diff = todayVal - target;
                const ratio = todayVal / target;
                if (diff > 0) {
                    diffEl.innerHTML = '今日热量超标 <span class="diff-value">' + diff.toFixed(1) + '</span> 千卡';
                    diffEl.className = 'diff-hint danger';
                } else if (ratio >= 0.9) {
                    diffEl.innerHTML = '今日还可以吃 <span class="diff-value">' + Math.abs(diff).toFixed(1) + '</span> 千卡食物';
                    diffEl.className = 'diff-hint warning';
                } else {
                    diffEl.innerHTML = '今日还可以吃 <span class="diff-value">' + Math.abs(diff).toFixed(1) + '</span> 千卡食物';
                    diffEl.className = 'diff-hint success';
                }
            }

            // ============================================================
            //  核心计算
            // ============================================================
            function computePreview() {
                const defaultW = getNumberValue(document.getElementById('defaultWeight'));
                const defaultUnit = document.getElementById('defaultUnit').value;
                const energy = getNumberValue(document.getElementById('energy'));
                const servingRaw = getNumberValue(document.getElementById('serving'));
                const servingUnit = document.getElementById('servingUnit').value;

                const hasDefault = !isNaN(defaultW) && defaultW > 0;
                const hasEnergy = !isNaN(energy) && energy >= 0;
                const hasServing = !isNaN(servingRaw) && servingRaw >= 0;

                let mealKcal = null;
                if (hasDefault && hasEnergy && hasServing) {
                    let servingUnits = servingRaw;
                    if (servingUnit === 'kg') servingUnits = servingRaw * 1000;
                    const energyPerUnit = energy / defaultW;
                    const totalKj = energyPerUnit * servingUnits;
                    mealKcal = totalKj / 4.184;
                }

                const todayStr = getTodayStr();
                const todayTotal = getTotalKcalForDate(todayStr);
                const target = getTargetDisplay();

                updateResults(mealKcal, todayTotal, target);
            }

            // ============================================================
            //  保存记录
            // ============================================================
            function saveRecord() {
                const name = document.getElementById('foodName').value.trim();
                const defaultW = getNumberValue(document.getElementById('defaultWeight'));
                const defaultUnit = document.getElementById('defaultUnit').value;
                const energy = getNumberValue(document.getElementById('energy'));
                const servingRaw = getNumberValue(document.getElementById('serving'));
                const servingUnit = document.getElementById('servingUnit').value;

                if (!name) { showToast('请填写食物名称', 'error'); return; }
                if (isNaN(defaultW) || defaultW <= 0) { showToast('请填写有效的每份量', 'error'); return; }
                if (isNaN(energy) || energy < 0) { showToast('请填写有效的能量 (kJ)', 'error'); return; }
                if (isNaN(servingRaw) || servingRaw <= 0) { showToast('请填写有效的食用量', 'error'); return; }

                let servingUnits = servingRaw;
                if (servingUnit === 'kg') servingUnits = servingRaw * 1000;

                const energyPerUnit = energy / defaultW;
                const totalKj = energyPerUnit * servingUnits;
                const totalKcal = totalKj / 4.184;

                const todayStr = getTodayStr();
                const meal = document.querySelector('.meal-btn.active')?.dataset.meal || '加餐';

                const record = {
                    id: generateId(),
                    tagName: name,
                    defaultWeight: defaultW,
                    unit: defaultUnit,
                    energy: energy,
                    serving: servingUnits,
                    servingUnit: servingUnit,
                    totalKcal: totalKcal,
                    meal: meal,
                    timestamp: new Date().toISOString()
                };

                if (!data.records[todayStr]) data.records[todayStr] = [];
                data.records[todayStr].push(record);

                const matchedTag = data.tags.find(t =>
                    t.name === name && t.defaultWeight === defaultW && t.unit === defaultUnit && t.energy === energy
                );
                if (matchedTag) {
                    matchedTag.lastUsed = Date.now();
                }

                saveData();

                document.getElementById('energy').value = '';
                document.getElementById('serving').value = '';
                computePreview();
                renderCalendar();
                // 如果当前选中的是今天,刷新记录卡片
                if (selectedDateStr === todayStr) {
                    renderRecordCard(todayStr);
                }
            }

            // ============================================================
            //  Toast 提示
            // ============================================================
            let toastTimer = null;

            function showToast(message, type) {
                const container = document.getElementById('toastContainer');
                const toast = document.createElement('div');
                toast.className = 'toast-item' + (type ? ' ' + type : '');
                toast.textContent = message;
                container.appendChild(toast);

                requestAnimationFrame(() => {
                    requestAnimationFrame(() => {
                        toast.classList.add('show');
                    });
                });

                setTimeout(() => {
                    toast.classList.remove('show');
                    setTimeout(() => {
                        if (toast.parentNode) {
                            toast.parentNode.removeChild(toast);
                        }
                    }, 300);
                }, 1500);
            }

            // ============================================================
            //  通用确认弹窗
            // ============================================================
            let confirmCallback = null;

            function showConfirm(title, desc, confirmText, onConfirm) {
                document.querySelector('#confirmModal .mc-title').textContent = title;
                document.getElementById('confirmDesc').textContent = desc;
                document.querySelector('#confirmModal .mc-confirm').textContent = confirmText;
                confirmCallback = onConfirm;
                document.getElementById('confirmModal').classList.add('show');
            }

            function closeConfirm() {
                confirmCallback = null;
                document.getElementById('confirmModal').classList.remove('show');
            }

            document.getElementById('confirmOk').addEventListener('click', function() {
                const cb = confirmCallback;
                closeConfirm();
                if (cb) cb();
            });

            document.getElementById('confirmCancel').addEventListener('click', closeConfirm);

            // ============================================================
            //  删除记录
            // ============================================================
            function confirmDeleteRecord(recordId) {
                const todayStr = getTodayStr();
                showConfirm('&#9888;&#65039; 确认删除', '确定要删除该记录吗?', '删除', function() {
                    const records = data.records[todayStr] || [];
                    const idx = records.findIndex(r => r.id === recordId);
                    if (idx !== -1) {
                        records.splice(idx, 1);
                        if (records.length === 0) {
                            delete data.records[todayStr];
                        }
                        saveData();
                        renderCalendar();
                        renderRecordCard(todayStr);
                        computePreview();
                    }
                });
            }

            // ============================================================
            //  渲染:日历 + 记录卡片
            // ============================================================
            let calYear = new Date().getFullYear();
            let calMonth = new Date().getMonth();
            let selectedDateStr = getTodayStr();

            function renderCalendar() {
                const grid = document.getElementById('calendarGrid');
                const monthLabel = document.getElementById('calMonth');
                const year = calYear,
                    month = calMonth;
                monthLabel.textContent = year + '年' + (month + 1) + '月';

                const firstDayOfMonth = new Date(year, month, 1).getDay();
                let startOffset = firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1;

                const daysInMonth = new Date(year, month + 1, 0).getDate();
                const daysInPrev = new Date(year, month, 0).getDate();

                const target = getTargetDisplay();
                const todayStr = getTodayStr();

                let html = '';
                const weekdays = ['一', '二', '三', '四', '五', '六', '日'];
                weekdays.forEach(w => {
                    html += `<div class="cal-weekday">${w}</div>`;
                });

                // 上月
                for (let i = startOffset - 1; i >= 0; i--) {
                    const d = daysInPrev - i;
                    const dateObj = new Date(year, month - 1, d);
                    const fullDate = formatDateFull(dateObj);
                    const shortDate = formatDateShort(dateObj);
                    const kcal = getTotalKcalForDate(fullDate);
                    const weight = getWeightForDate(fullDate);
                    html += `<div class="cal-day other-month" data-date="${fullDate}">
                                <span class="day-number">${d}</span>
                                ${kcal > 0 ? `<span class="day-kcal">${kcal.toFixed(0)}</span>` : ''}
                                ${weight !== null ? `<span class="day-weight">${weight.toFixed(1)}</span>` : ''}
                             </div>`;
                }

                // 本月
                for (let d = 1; d <= daysInMonth; d++) {
                    const dateObj = new Date(year, month, d);
                    const fullDate = formatDateFull(dateObj);
                    const shortDate = formatDateShort(dateObj);
                    const kcal = getTotalKcalForDate(fullDate);
                    const weight = getWeightForDate(fullDate);
                    const isToday = fullDate === todayStr;
                    const isSelected = fullDate === selectedDateStr;
                    let statusClass = '';
                    if (kcal > 0 && target !== null && target > 0) {
                        const ratio = kcal / target;
                        if (ratio > 1.0) statusClass = 'status-red';
                        else if (ratio >= 0.9) statusClass = 'status-yellow';
                        else statusClass = 'status-green';
                    } else if (kcal > 0) {
                        statusClass = 'status-green';
                    }
                    let cls = 'cal-day';
                    if (isToday) cls += ' today';
                    if (isSelected) cls += ' selected';
                    if (statusClass) cls += ' ' + statusClass;
                    html += `<div class="${cls}" data-date="${fullDate}">
                                <span class="day-number">${d}</span>
                                ${kcal > 0 ? `<span class="day-kcal">${kcal.toFixed(0)}</span>` : ''}
                                ${weight !== null ? `<span class="day-weight">${weight.toFixed(1)}</span>` : ''}
                             </div>`;
                }

                // 下月填充
                const totalCells = startOffset + daysInMonth;
                const remaining = (7 - (totalCells % 7)) % 7;
                for (let d = 1; d <= remaining; d++) {
                    const dateObj = new Date(year, month + 1, d);
                    const fullDate = formatDateFull(dateObj);
                    const shortDate = formatDateShort(dateObj);
                    const kcal = getTotalKcalForDate(fullDate);
                    const weight = getWeightForDate(fullDate);
                    html += `<div class="cal-day other-month" data-date="${fullDate}">
                                <span class="day-number">${d}</span>
                                ${kcal > 0 ? `<span class="day-kcal">${kcal.toFixed(0)}</span>` : ''}
                                ${weight !== null ? `<span class="day-weight">${weight.toFixed(1)}</span>` : ''}
                             </div>`;
                }

                grid.innerHTML = html;

                // 点击日期
                grid.querySelectorAll('.cal-day').forEach(el => {
                    el.addEventListener('click', function() {
                        const fullDate = this.dataset.date;
                        if (fullDate) {
                            selectedDateStr = fullDate;
                            renderCalendar();
                            renderRecordCard(fullDate);
                        }
                    });
                });

                // 如果当前选中日期有记录,刷新记录卡片
                if (selectedDateStr) {
                    renderRecordCard(selectedDateStr);
                }
            }

            function formatDateFull(d) {
                const y = d.getFullYear();
                const m = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                return y + '-' + m + '-' + day;
            }

            function renderRecordCard(dateStr) {
                const card = document.getElementById('recordCard');
                const list = document.getElementById('ddList');
                const title = document.getElementById('ddTitle');
                const weightEl = document.getElementById('ddWeight');
                const records = getRecordsForDate(dateStr);
                const total = getTotalKcalForDate(dateStr);
                const weight = getWeightForDate(dateStr);
                const todayStr = getTodayStr();
                const isToday = (dateStr === todayStr);

                if (records.length === 0 && weight === null) {
                    card.classList.remove('show');
                    return;
                }

                card.classList.add('show');
                title.innerHTML = '&#128203; ' + dateStr + '  ·  总计 <span class="dd-total-kcal">' + total.toFixed(1) + ' 千卡</span>';
                if (weight !== null) {
                    weightEl.textContent = '&#9878;&#65039; ' + weight.toFixed(1) + ' kg';
                    weightEl.style.display = 'block';
                } else {
                    weightEl.style.display = 'none';
                }

                if (records.length === 0) {
                    list.innerHTML = '<div class="dd-empty">暂无饮食记录</div>';
                    return;
                }

                let html = '';
                const mealOrder = ['早餐', '中餐', '晚餐', '加餐'];
                const grouped = {};
                records.forEach(r => {
                    const m = r.meal || '加餐';
                    if (!grouped[m]) grouped[m] = [];
                    grouped[m].push(r);
                });
                mealOrder.forEach(m => {
                    if (grouped[m] && grouped[m].length > 0) {
                        html += `<div class="meal-group-label">${m}</div>`;
                        grouped[m].forEach(r => {
                            const name = r.tagName || '未命名';
                            const kcal = r.totalKcal || 0;
                            const gram = r.serving || 0;
                            const unit = r.unit || 'g';
                            let delBtn = '';
                            if (isToday) {
                                delBtn = `<button class="dd-del" data-id="${r.id}">&#10005;</button>`;
                            }
                            html += `<div class="dd-item">
                                        <span class="dd-name">${escapeHtml(name)} (${gram}${unit})</span>
                                        <span style="display:flex;align-items:center;">
                                            <span class="dd-kcal">${kcal.toFixed(1)} 千卡</span>
                                            ${delBtn}
                                        </span>
                                     </div>`;
                        });
                    }
                });
                list.innerHTML = html;

                if (isToday) {
                    list.querySelectorAll('.dd-del').forEach(btn => {
                        btn.addEventListener('click', function(e) {
                            e.stopPropagation();
                            const id = this.dataset.id;
                            confirmDeleteRecord(id);
                        });
                    });
                }
            }

            // ============================================================
            //  标签系统(简化版)
            // ============================================================
            const tagListEl = document.getElementById('tagList');
            const tagCountEl = document.getElementById('tagCount');

            function renderTags() {
                const tags = [...data.tags].sort((a, b) => (b.lastUsed || 0) - (a.lastUsed || 0));
                tagCountEl.textContent = tags.length + ' / 50';

                if (tags.length === 0) {
                    tagListEl.innerHTML =
                        '<div style="color:#b0b8c4;text-align:center;padding:16px 0;font-size:13px;">暂无标签</div>';
                    return;
                }

                let html = '';
                tags.forEach((tag) => {
                    const unitDisplay = tag.unit || 'g';
                    html += `<div class="tag-item" data-id="${tag.id}">
                                <span class="tag-name">${escapeHtml(tag.name)}</span>
                                <span style="font-size:10px;color:#999;flex-shrink:0;">${tag.defaultWeight}${unitDisplay}/${tag.energy}kJ</span>
                                <button class="tag-del" data-id="${tag.id}">&#10005;</button>
                             </div>`;
                });
                tagListEl.innerHTML = html;

                tagListEl.querySelectorAll('.tag-del').forEach(btn => {
                    btn.addEventListener('click', function(e) {
                        e.stopPropagation();
                        const id = this.dataset.id;
                        const tag = data.tags.find(t => t.id === id);
                        if (tag) {
                            const tagId = id;
                            const tagName = tag.name;
                            showConfirm('&#9888;&#65039; 确认删除', '确定要删除标签 "' + tagName + '" 吗?', '删除', function() {
                                data.tags = data.tags.filter(t => t.id !== tagId);
                                saveData();
                                renderTags();
                                showToast('&#9989; 标签已删除', 'success');
                            });
                        }
                    });
                });

                tagListEl.querySelectorAll('.tag-item').forEach(el => {
                    el.addEventListener('click', function(e) {
                        if (e.target.closest('.tag-del')) return;
                        const id = this.dataset.id;
                        const tag = data.tags.find(t => t.id === id);
                        if (tag) {
                            tagListEl.querySelectorAll('.tag-item').forEach(item => {
                                item.classList.remove('tag-selected');
                            });
                            this.classList.add('tag-selected');
                            setTimeout(() => {
                                document.getElementById('foodName').value = tag.name;
                                document.getElementById('defaultWeight').value = tag.defaultWeight;
                                document.getElementById('defaultUnit').value = tag.unit || 'g';
                                document.getElementById('energy').value = tag.energy;
                                const unit = tag.unit || 'g';
                                document.getElementById('servingUnit').value = (unit === 'ml') ? 'ml' : 'g';
                                computePreview();
                                closePanel();
                            }, 150);
                        }
                    });
                });
            }

            // ============================================================
            //  标签面板控制
            // ============================================================
            const fabBtn = document.getElementById('fabToggle');
            const panel = document.getElementById('tagPanel');
            const overlayTag = document.getElementById('overlayTag');
            const panelClose = document.getElementById('panelClose');

            function openPanel() {
                panel.classList.add('open');
                overlayTag.classList.add('show');
                renderTags();
            }

            function closePanel() {
                panel.classList.remove('open');
                overlayTag.classList.remove('show');
            }

            fabBtn.addEventListener('click', function() {
                if (panel.classList.contains('open')) closePanel();
                else openPanel();
            });
            panelClose.addEventListener('click', closePanel);
            overlayTag.addEventListener('click', closePanel);

            // ============================================================
            //  悬浮按钮拖动
            // ============================================================
            const tagFab = document.getElementById('tagFab');
            const FAB_POS_KEY = 'tagFabPos';

            function loadFabPos() {
                try {
                    const saved = localStorage.getItem(FAB_POS_KEY);
                    if (saved) {
                        const pos = JSON.parse(saved);
                        if (pos.right !== undefined) tagFab.style.right = pos.right + 'px';
                        if (pos.bottom !== undefined) tagFab.style.bottom = pos.bottom + 'px';
                        if (pos.left !== undefined) {
                            tagFab.style.left = pos.left + 'px';
                            tagFab.style.right = 'auto';
                        }
                    }
                } catch (_) {}
            }

            function saveFabPos(right, bottom, left) {
                try {
                    const pos = {};
                    if (left !== undefined) pos.left = left;
                    else pos.right = right;
                    pos.bottom = bottom;
                    localStorage.setItem(FAB_POS_KEY, JSON.stringify(pos));
                } catch (_) {}
            }

            let isDragging = false;
            let dragMoved = false;
            let startX = 0;
            let startY = 0;
            let startRight = 0;
            let startBottom = 0;
            let startLeft = 0;
            let isLeftSide = false;

            function onDragStart(e) {
                isDragging = true;
                dragMoved = false;
                const rect = tagFab.getBoundingClientRect();
                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;
                startX = clientX;
                startY = clientY;
                startRight = window.innerWidth - rect.right;
                startBottom = window.innerHeight - rect.bottom;
                startLeft = rect.left;
                isLeftSide = (rect.left < window.innerWidth / 2);
                tagFab.style.transition = 'none';
            }

            function onDragMove(e) {
                if (!isDragging) return;
                e.preventDefault();
                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;
                const dx = clientX - startX;
                const dy = clientY - startY;
                if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
                    dragMoved = true;
                }
                let newLeft, newRight, newBottom;
                if (isLeftSide) {
                    newLeft = startLeft + dx;
                    newLeft = Math.max(4, Math.min(window.innerWidth - 56, newLeft));
                    tagFab.style.left = newLeft + 'px';
                    tagFab.style.right = 'auto';
                } else {
                    newRight = startRight - dx;
                    newRight = Math.max(4, Math.min(window.innerWidth - 56, newRight));
                    tagFab.style.right = newRight + 'px';
                    tagFab.style.left = 'auto';
                }
                newBottom = startBottom - dy;
                newBottom = Math.max(4, Math.min(window.innerHeight - 56, newBottom));
                tagFab.style.bottom = newBottom + 'px';
            }

            function onDragEnd(e) {
                if (!isDragging) return;
                isDragging = false;
                tagFab.style.transition = '';
                if (dragMoved) {
                    const rect = tagFab.getBoundingClientRect();
                    const bottom = window.innerHeight - rect.bottom;
                    const isLeft = rect.left < window.innerWidth / 2;
                    if (isLeft) {
                        saveFabPos(undefined, bottom, rect.left);
                    } else {
                        const right = window.innerWidth - rect.right;
                        saveFabPos(right, bottom, undefined);
                    }
                }
            }

            fabBtn.addEventListener('mousedown', function(e) {
                onDragStart(e);
            });
            document.addEventListener('mousemove', function(e) {
                onDragMove(e);
            });
            document.addEventListener('mouseup', function(e) {
                onDragEnd(e);
            });

            fabBtn.addEventListener('touchstart', function(e) {
                onDragStart(e);
            }, { passive: false });
            document.addEventListener('touchmove', function(e) {
                onDragMove(e);
            }, { passive: false });
            document.addEventListener('touchend', function(e) {
                onDragEnd(e);
            });

            fabBtn.addEventListener('click', function(e) {
                if (dragMoved) {
                    e.stopImmediatePropagation();
                    e.preventDefault();
                }
            }, true);

            loadFabPos();

            // ============================================================
            //  设置弹窗
            // ============================================================
            const settingsModal = document.getElementById('settingsModal');
            const settingsToggle = document.getElementById('settingsToggle');
            const settingsClose = document.getElementById('settingsClose');
            const settingsSave = document.getElementById('settingsSave');

            function openSettings() {
                document.getElementById('userAge').value = userSettings.age || '';
                document.getElementById('userGenderSelect').value = userSettings.gender || 'male';
                document.getElementById('userHeight').value = userSettings.height || '';
                document.getElementById('userWeight').value = userSettings.weight || '';
                document.getElementById('userActivity').value = userSettings.activity || '1.2';
                const target = userSettings.target || 'lose';
                document.querySelectorAll('#targetGroup button').forEach(btn => {
                    btn.classList.toggle('active', btn.dataset.target === target);
                });
                updateCalcInfo();
                settingsModal.classList.add('show');
            }

            function closeSettings() {
                settingsModal.classList.remove('show');
            }

            settingsToggle.addEventListener('click', openSettings);
            settingsClose.addEventListener('click', closeSettings);
            settingsModal.addEventListener('click', function(e) {
                if (e.target === this) closeSettings();
            });

            document.querySelectorAll('#targetGroup button').forEach(btn => {
                btn.addEventListener('click', function() {
                    document.querySelectorAll('#targetGroup button').forEach(b => b.classList.remove('active'));
                    this.classList.add('active');
                    userSettings.target = this.dataset.target;
                    updateCalcInfo();
                });
            });

            ['userAge', 'userHeight', 'userWeight', 'userActivity', 'userGenderSelect'].forEach(id => {
                const el = document.getElementById(id);
                el.addEventListener('input', updateCalcInfo);
                el.addEventListener('change', updateCalcInfo);
            });

            function updateCalcInfo() {
                const age = parseInt(document.getElementById('userAge').value);
                const gender = document.getElementById('userGenderSelect').value;
                const height = parseFloat(document.getElementById('userHeight').value);
                const weight = parseFloat(document.getElementById('userWeight').value);
                const activity = parseFloat(document.getElementById('userActivity').value);
                const target = document.querySelector('#targetGroup .active')?.dataset.target || 'lose';

                const bmrEl = document.getElementById('bmrDisplay');
                const tdeeEl = document.getElementById('tdeeDisplay');
                const suggEl = document.getElementById('targetSuggestion');

                if (isNaN(age) || isNaN(height) || isNaN(weight) || age <= 0 || height <= 0 || weight <= 0) {
                    bmrEl.textContent = '--';
                    tdeeEl.textContent = '--';
                    suggEl.innerHTML = '请填写完整信息';
                    return;
                }

                let bmr;
                if (gender === 'male') {
                    bmr = 10 * weight + 6.25 * height - 5 * age + 5;
                } else {
                    bmr = 10 * weight + 6.25 * height - 5 * age - 161;
                }
                const tdee = bmr * activity;
                let targetKcal = (target === 'maintain') ? tdee : (tdee - 400);
                if (targetKcal < 1000) targetKcal = 1000;

                bmrEl.textContent = Math.round(bmr);
                tdeeEl.textContent = Math.round(tdee);
                suggEl.innerHTML = '建议每日摄入 <strong>' + Math.round(targetKcal) + '</strong> 千卡 (' + (target === 'maintain' ?
                    '维持' : '减肥') + ')';
            }

            settingsSave.addEventListener('click', function() {
                const age = parseInt(document.getElementById('userAge').value);
                const gender = document.getElementById('userGenderSelect').value;
                const height = parseFloat(document.getElementById('userHeight').value);
                const weight = parseFloat(document.getElementById('userWeight').value);
                const activity = parseFloat(document.getElementById('userActivity').value);
                const target = document.querySelector('#targetGroup .active')?.dataset.target || 'lose';

                if (isNaN(age) || isNaN(height) || isNaN(weight) || age <= 0 || height <= 0 || weight <= 0) {
                    showToast('请完整填写年龄、身高、体重!', 'error');
                    return;
                }

                const settings = { age, gender, height, weight, activity, target };
                saveSettings(settings);
                closeSettings();
                computePreview();
                renderCalendar();
            });

            // ============================================================
            //  主题弹窗
            // ============================================================
            const themeModal = document.getElementById('themeModal');
            const themeToggle = document.getElementById('themeToggle');
            const themeClose = document.getElementById('themeClose');
            const themeGrid = document.getElementById('themeGrid');

            function renderThemeGrid() {
                let html = '';
                THEMES.forEach(t => {
                    const [start, end] = t.colors;
                    const active = (t.id === currentTheme) ? ' active' : '';
                    html += `<div class="theme-btn${active}" data-theme="${t.id}">
                                <div class="color-preview" style="background:linear-gradient(135deg, ${start}, ${end});"></div>
                                ${t.name}
                             </div>`;
                });
                themeGrid.innerHTML = html;

                themeGrid.querySelectorAll('.theme-btn').forEach(btn => {
                    btn.addEventListener('click', function() {
                        const themeId = this.dataset.theme;
                        applyTheme(themeId);
                        themeGrid.querySelectorAll('.theme-btn').forEach(b => b.classList.remove('active'));
                        this.classList.add('active');
                    });
                });
            }

            function openThemeModal() {
                renderThemeGrid();
                themeModal.classList.add('show');
            }

            themeToggle.addEventListener('click', openThemeModal);
            themeClose.addEventListener('click', function() {
                themeModal.classList.remove('show');
            });
            themeModal.addEventListener('click', function(e) {
                if (e.target === this) themeModal.classList.remove('show');
            });

            // ============================================================
            //  备份弹窗
            // ============================================================
            const backupModal = document.getElementById('backupModal');
            const backupToggle = document.getElementById('backupToggle');
            const backupExportBtn = document.getElementById('backupExportBtn');
            const backupImportBtn = document.getElementById('backupImportBtn');
            const backupFileInput = document.getElementById('backupFileInput');

            function openBackupModal() {
                backupModal.classList.add('show');
            }

            function closeBackupModal() {
                backupModal.classList.remove('show');
            }

            backupToggle.addEventListener('click', openBackupModal);
            backupModal.addEventListener('click', function(e) {
                if (e.target === this) closeBackupModal();
            });
            backupExportBtn.addEventListener('click', doExport);
            backupImportBtn.addEventListener('click', function() {
                backupFileInput.click();
            });
            backupFileInput.addEventListener('change', function(e) {
                const file = e.target.files[0];
                if (file) {
                    handleImportFile(file);
                }
                e.target.value = '';
            });

            // ============================================================
            //  体重弹窗
            // ============================================================
            const weightModal = document.getElementById('weightModal');
            const weightToggle = document.getElementById('weightToggle');
            const weightClose = document.getElementById('weightClose');
            const weightSave = document.getElementById('weightSave');
            const weightInput = document.getElementById('weightInput');
            const weightTime = document.getElementById('weightTime');
            const weightJinDisplay = document.getElementById('weightJinDisplay');

            weightInput.addEventListener('input', function() {
                const val = parseFloat(this.value);
                if (!isNaN(val) && val > 0) {
                    weightJinDisplay.textContent = (val * 2).toFixed(1) + ' 斤';
                } else {
                    weightJinDisplay.textContent = '';
                }
            });

            function openWeightModal() {
                const today = getTodayStr();
                const existing = getWeightForDate(today);
                if (existing !== null) {
                    weightInput.value = existing;
                    weightJinDisplay.textContent = (existing * 2).toFixed(1) + ' 斤';
                } else {
                    weightInput.value = '';
                    weightJinDisplay.textContent = '';
                }
                weightTime.value = '自动';
                drawWeightChart();
                weightModal.classList.add('show');
            }

            function closeWeightModal() {
                weightModal.classList.remove('show');
            }

            weightToggle.addEventListener('click', openWeightModal);
            weightClose.addEventListener('click', closeWeightModal);
            weightModal.addEventListener('click', function(e) {
                if (e.target === this) closeWeightModal();
            });

            weightSave.addEventListener('click', function() {
                const val = parseFloat(weightInput.value);
                if (isNaN(val) || val <= 0) {
                    showToast('请输入有效的体重 (kg)', 'error');
                    return;
                }
                const today = getTodayStr();
                setWeightForDate(today, val);
                showToast('&#9989; 体重已保存: ' + val + ' kg (' + (val * 2).toFixed(1) + ' 斤)', 'success');
                drawWeightChart();
                renderRecordCard(today);
                closeWeightModal();
            });

            // ============================================================
            //  体重图表
            // ============================================================
            const weightChartCanvas = document.getElementById('weightChartCanvas');
            const weightNoData = document.getElementById('weightNoData');
            const weightPeriodBtns = document.querySelectorAll('#weightPeriodGroup button');
            let weightPeriod = 7;

            function drawWeightChart() {
                const canvas = weightChartCanvas;
                const ctx = canvas.getContext('2d');
                const container = document.getElementById('weightChartContainer');
                const width = container.clientWidth - 4 || 300;
                const height = 180;
                canvas.width = width * 0.98;
                canvas.height = height;
                canvas.style.width = (width * 0.98) + 'px';
                canvas.style.height = height + 'px';

                const range = getRecentRange(weightPeriod);
                const startFull = formatDateFull(range.start);
                const endFull = formatDateFull(range.end);
                const records = getWeightRecordsInRange(startFull, endFull);

                if (records.length === 0) {
                    weightNoData.style.display = 'block';
                    ctx.clearRect(0, 0, canvas.width, canvas.height);
                    return;
                }
                weightNoData.style.display = 'none';

                const padding = { top: 20, bottom: 22, left: 36, right: 16 };
                const chartWidth = canvas.width - padding.left - padding.right;
                const chartHeight = canvas.height - padding.top - padding.bottom;

                let minWeight = Infinity,
                    maxWeight = -Infinity;
                records.forEach(r => {
                    if (r.weight < minWeight) minWeight = r.weight;
                    if (r.weight > maxWeight) maxWeight = r.weight;
                });
                const rangeWeight = maxWeight - minWeight;
                const yMin = Math.max(0, minWeight - rangeWeight * 0.15);
                const yMax = maxWeight + rangeWeight * 0.15;

                ctx.clearRect(0, 0, canvas.width, canvas.height);

                ctx.strokeStyle = '#e8ecf1';
                ctx.lineWidth = 0.5;
                const ySteps = 4;
                for (let i = 0; i <= ySteps; i++) {
                    const y = padding.top + chartHeight - (i / ySteps) * chartHeight;
                    ctx.beginPath();
                    ctx.moveTo(padding.left, y);
                    ctx.lineTo(canvas.width - padding.right, y);
                    ctx.stroke();
                    const value = yMin + (i / ySteps) * (yMax - yMin);
                    ctx.fillStyle = '#888';
                    ctx.font = '9px sans-serif';
                    ctx.textAlign = 'right';
                    ctx.textBaseline = 'middle';
                    ctx.fillText(value.toFixed(1), padding.left - 4, y);
                }

                ctx.strokeStyle = '#ff7e5f';
                ctx.lineWidth = 2.5;
                ctx.beginPath();
                records.forEach((r, idx) => {
                    const x = padding.left + (idx / (records.length - 1 || 1)) * chartWidth;
                    const y = padding.top + chartHeight - ((r.weight - yMin) / (yMax - yMin)) * chartHeight;
                    if (idx === 0) ctx.moveTo(x, y);
                    else ctx.lineTo(x, y);
                });
                ctx.stroke();

                records.forEach((r, idx) => {
                    const x = padding.left + (idx / (records.length - 1 || 1)) * chartWidth;
                    const y = padding.top + chartHeight - ((r.weight - yMin) / (yMax - yMin)) * chartHeight;
                    ctx.beginPath();
                    ctx.arc(x, y, 3.5, 0, 2 * Math.PI);
                    ctx.fillStyle = '#ff7e5f';
                    ctx.fill();
                    ctx.strokeStyle = '#fff';
                    ctx.lineWidth = 1.5;
                    ctx.stroke();
                });

                let labelInterval = 1;
                if (weightPeriod > 7 && weightPeriod <= 30) labelInterval = Math.ceil(weightPeriod / 7);
                else if (weightPeriod > 30 && weightPeriod <= 90) labelInterval = Math.ceil(weightPeriod / 15);
                else if (weightPeriod > 90 && weightPeriod <= 180) labelInterval = Math.ceil(weightPeriod / 30);
                else if (weightPeriod > 180) labelInterval = Math.ceil(weightPeriod / 60);

                ctx.fillStyle = '#888';
                ctx.font = '9px sans-serif';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'top';
                records.forEach((r, idx) => {
                    if (idx % labelInterval === 0 || idx === records.length - 1) {
                        const x = padding.left + (idx / (records.length - 1 || 1)) * chartWidth;
                        const parts = r.date.split('-');
                        const label = parts[1] + '/' + parts[2];
                        ctx.fillText(label, x, canvas.height - padding.bottom + 2);
                    }
                });
            }

            weightPeriodBtns.forEach(btn => {
                btn.addEventListener('click', function() {
                    weightPeriodBtns.forEach(b => b.classList.remove('active'));
                    this.classList.add('active');
                    weightPeriod = parseInt(this.dataset.period);
                    drawWeightChart();
                });
            });

            let resizeTimer;
            window.addEventListener('resize', function() {
                clearTimeout(resizeTimer);
                resizeTimer = setTimeout(() => {
                    if (weightModal.classList.contains('show')) {
                        drawWeightChart();
                    }
                }, 200);
            });

            // ============================================================
            //  单位联动 + 事件绑定
            // ============================================================
            document.getElementById('defaultUnit').addEventListener('change', function() {
                const servingUnit = document.getElementById('servingUnit');
                if (this.value === 'ml') {
                    servingUnit.value = 'ml';
                } else {
                    servingUnit.value = 'g';
                }
                computePreview();
            });

            document.getElementById('servingUnit').addEventListener('change', computePreview);

            ['foodName', 'defaultWeight', 'energy', 'serving'].forEach(id => {
                const el = document.getElementById(id);
                el.addEventListener('input', function() {
                    computePreview();
                    saveInputCache();
                });
                el.addEventListener('change', function() {
                    computePreview();
                    saveInputCache();
                });
            });
            ['defaultUnit', 'servingUnit'].forEach(id => {
                const el = document.getElementById(id);
                el.addEventListener('change', saveInputCache);
            });

            document.querySelectorAll('.meal-btn').forEach(btn => {
                btn.addEventListener('click', function() {
                    document.querySelectorAll('.meal-btn').forEach(b => b.classList.remove('active'));
                    this.classList.add('active');
                });
            });
            const autoMeal = getAutoMeal();
            document.querySelectorAll('.meal-btn').forEach(btn => {
                if (btn.dataset.meal === autoMeal) btn.classList.add('active');
            });

            document.getElementById('saveRecordBtn').addEventListener('click', saveRecord);

            document.getElementById('saveTagQuick').addEventListener('click', function() {
                const name = document.getElementById('foodName').value.trim();
                const weight = getNumberValue(document.getElementById('defaultWeight'));
                const unit = document.getElementById('defaultUnit').value;
                const energy = getNumberValue(document.getElementById('energy'));

                if (!name) { showToast('请填写食物名称', 'error'); return; }
                if (isNaN(weight) || weight <= 0) { showToast('请填写有效的每份量', 'error'); return; }
                if (isNaN(energy) || energy < 0) { showToast('请填写有效的能量 (kJ)', 'error'); return; }

                if (data.tags.length >= 50) { showToast('标签已达上限 (50)', 'warning'); return; }

                const exist = data.tags.find(t => t.name === name && t.unit === unit && t.defaultWeight === weight && t
                    .energy === energy);
                if (exist) {
                    exist.lastUsed = Date.now();
                    saveData();
                    renderTags();
                    showToast('标签已存在', 'warning');
                    return;
                }

                const sameName = data.tags.find(t => t.name === name);
                if (sameName) {
                    showConfirm('&#9888;&#65039; 确认覆盖', '已存在同名标签,是否覆盖?', '覆盖', function() {
                        sameName.defaultWeight = weight;
                        sameName.unit = unit;
                        sameName.energy = energy;
                        sameName.lastUsed = Date.now();
                        saveData();
                        renderTags();
                        showToast('&#9989; 已更新: ' + name, 'success');
                    });
                    return;
                }

                data.tags.unshift({
                    id: generateId(),
                    name: name,
                    defaultWeight: weight,
                    unit: unit,
                    energy: energy,
                    lastUsed: Date.now()
                });
                saveData();
                renderTags();
                showToast('&#9989; 已保存: ' + name, 'success');
            });

            document.getElementById('calPrev').addEventListener('click', function() {
                calMonth--;
                if (calMonth < 0) { calMonth = 11;
                    calYear--; }
                renderCalendar();
            });
            document.getElementById('calNext').addEventListener('click', function() {
                calMonth++;
                if (calMonth > 11) { calMonth = 0;
                    calYear++; }
                renderCalendar();
            });

            // ============================================================
            //  初始化
            // ============================================================
            function init() {
                applyTheme(currentTheme);
                updateDateDisplay();
                applyInputCache();
                renderCalendar();
                computePreview();

                setTimeout(() => {
                    drawWeightChart();
                }, 100);

                console.log('&#127857; 热量计算+体重记录 已启动 (紧凑日历+差值提示)');
            }

            init();

            setInterval(() => {
                const todayStr = getTodayStr();
                const todayTotal = getTotalKcalForDate(todayStr);
                const target = getTargetDisplay();
                updateResults(null, todayTotal, target);
                if (selectedDateStr === todayStr) {
                    renderRecordCard(todayStr);
                }
            }, 60000);

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

自用手机端热量&体重记录单页工具

(本程序由人工智能编程辅助完成)

工具简介

这是一套纯前端单页HTML工具,无需安装软件、不用联网,直接保存文件到手机本地打开即可使用,全程适配移动端屏幕尺寸,针对手机浏览、输入操作做了完整优化,所有数据仅存储在本机浏览器本地存储,不会上传任何服务器,隐私性有保障。

核心功能模块说明

一、饮食热量计算模块

  1. 自动匹配当日时段默认餐次,分为早餐、中餐、晚餐、加餐四类记录,可手动切换餐次分类。
  2. 自定义食物参数录入,支持填写食物名称、基准份量(克/毫升)、每基准份量千焦能量、实际食用量,食用量支持克、千克、毫升单位切换。
  3. 实时计算当前输入食物热量,自动汇总当日全部饮食总热量。
  4. 内置50条常用食材预设标签,涵盖肉类、蛋奶、主食、蔬果、豆制品,可一键填充食材参数,减少重复输入。
  5. 支持新增自定义食材标签,上限50个,可删除、复用,系统会自动优先展示近期使用过的食材。
  6. 单条饮食记录仅支持删除当日记录,历史日期记录仅查看不可修改删除,避免误操作丢失过往数据。

二、个人基础代谢与目标热量测算模块

  1. 填写年龄、性别、身高、体重、日常运动量,自动计算基础代谢BMR、每日总消耗TDEE。
  2. 提供两种目标模式:维持体重、减脂,减脂模式默认每日热量缺口400千卡,最低摄入限制1000千卡。
  3. 实时展示当日摄入热量与目标热量差值,区分三种状态提示:热量充足、接近目标、热量超标。
  4. 所有个人基础信息本地保存,重启页面无需重复填写。

三、日历数据总览模块

  1. 按月展示完整日历视图,每个日期格子标注当日总热量、当日记录体重。
  2. 热量状态自动区分三种底色:摄入合理、接近目标、超标,直观分辨每日饮食情况。
  3. 支持切换上下月份,点击任意日期即可查看当天完整饮食记录与体重数据。
  4. 自动区分当月日期、上月/下月空白日期,当天日期边框特殊标记,选中日期增加高亮标识。

四、体重记录与趋势图表模块

  1. 独立体重录入面板,输入公斤数值自动换算对应斤数,可标注称重时段。
  2. 体重数据永久绑定对应日期,日历与当日记录页面同步展示体重数值。
  3. 内置趋势折线图,可选7天、半月、一个月、三个月、半年、一年六种时间范围查看体重变化曲线。
  4. 图表自适应手机屏幕尺寸,无体重数据时会显示空白提示。

五、主题自定义模块

  1. 内置12套渐变配色主题,一键切换页面整体视觉风格,主题配置自动本地保存。
  2. 原生适配系统深色模式,跟随手机明暗系统自动切换页面配色,文字、背景、输入框全部适配暗色显示。

六、数据导入导出备份模块

  1. 完整本地备份功能,可自主选择导出内容:个人信息、主题配置、自定义食材标签、全部饮食记录、全部体重记录。
  2. 导出文件为标准JSON格式,文件名自带导出时间,方便归档留存。
  3. 支持导入备份文件,导入后覆盖对应分类本地数据,可更换设备迁移全部记录。

七、细节优化与手机适配

  1. 全页面响应式布局,小屏手机自动缩小输入框、按钮、日历格子,大屏设备放大显示内容。
  2. 输入框、按钮缩小点击区域误触概率,点击带有缩放反馈,无多余弹窗干扰。
  3. 输入内容自动缓存,刷新页面不会清空正在编辑的食材参数。
  4. 自动每分钟刷新当日热量统计,长期打开页面无需手动刷新查看最新数据。
  5. 操作弹窗、提示文字全部适配手机阅读,无横向滚动条,仅纵向滑动浏览页面。

使用注意事项

  1. 使用方式:将完整代码复制保存为后缀为html的文件,传输到手机,用手机自带浏览器打开,不建议使用微信、QQ内置浏览器,内置浏览器存在本地存储丢失、文件无法保存的问题。
  2. 数据保存规则:所有饮食、体重、个人信息、自定义标签全部存储在浏览器本地存储,清除浏览器缓存、卸载浏览器、无痕模式打开会清空全部数据,重要数据建议定期导出备份。
  3. 标签上限:自定义食材标签最多保存50个,达到上限后无法新增,可删除不常用标签再添加新食材。
  4. 数据修改限制:仅当日饮食记录支持删除,过往日期记录仅可查看,无法修改删除,如需调整历史数据只能通过备份文件修改后重新导入。
  5. 单位换算说明:工具内能量输入单位为千焦,页面展示自动换算千卡,换算标准为1千卡等于4.184千焦。
  6. 体重记录规则:同一日期仅能保存一条体重数据,重复录入会直接覆盖当日原有体重记录。
  7. 深色模式生效条件:页面会跟随手机系统明暗设置自动切换,部分浏览器需要重启页面才能加载暗色样式。
  8. 备份文件仅支持JSON格式,导入时选择其他格式文件会提示读取失败,导入前确认文件未被修改后缀。
  9. 工具仅本地离线运行,无联网请求,不存在上传数据、隐私泄露风险,但同时不支持云端同步,更换设备必须手动导出导入备份文件。
  10. 页面长期后台挂机会轻微占用手机内存,长时间不用建议关闭页面,避免浏览器卡顿。

补充说明

工具完全开源免费,无广告、无付费功能,仅做个人健康记录使用,可自行修改代码调整配色、预设食材、计算规则,禁止二次打包收费售卖。

微信图片_20260725202112_92_61.jpg (182.38 KB, 下载次数: 0)

微信图片_20260725202112_92_61.jpg

微信图片_20260725202115_94_61.jpg (135.21 KB, 下载次数: 0)

微信图片_20260725202115_94_61.jpg

微信图片_20260725202116_95_61.jpg (154.46 KB, 下载次数: 0)

微信图片_20260725202116_95_61.jpg

微信图片_20260725202118_96_61.jpg (147.65 KB, 下载次数: 0)

微信图片_20260725202118_96_61.jpg

微信图片_20260725202136_97_61.jpg (153.99 KB, 下载次数: 0)

微信图片_20260725202136_97_61.jpg

微信图片_20260725202114_93_61.jpg (166.48 KB, 下载次数: 0)

微信图片_20260725202114_93_61.jpg

热量计算器 体重记录.txt

139.04 KB, 下载次数: 27, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 4吾爱币 +9 热心值 +4 收起 理由
kyoth + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
zzldy4 + 1 谢谢@Thanks!
FASTBLUE + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| liangzaihuigui 发表于 2026-7-26 01:03 |楼主
更新了一些小bug,

【题外话】经过我两次减肥经验来看(都减肥成功,但是后面反弹),我反对极端的短期减肥,我信奉减肥长期主义,要走可持续的道路,要有一个能长期持续下去的饮食计划,这个程序就是这个计划的一种监督和结果的数字化呈现。
所以不把具体体重数字当做目标,而是长期保持一种状态(呈现出来就是稳定的热量和均衡的营养),让身体自然而然恢复到理想合适的体重(事前不知道具体是多少)。所以如果你减肥前预设了一个具体数字,这并不科学。
当然我反对苦行僧式的计划,也反对极端放纵、心里没数的理念,我主账在美食欲望和身体健康之间取得平衡,这两者并非非此即彼。
美食要不要吃?当然要吃!不可辜负,但是不能放纵,要浅尝即止。
说多了,不说了!

更新日志

  • 扫帚清空按钮:输入区顶部新增 🧹 按钮,一键清空所有输入框
  • 餐次下拉选择:早餐/中餐/晚餐/加餐改为下拉选择框,更节省空间
  • 暗黑模式切换:增加暗黑模式切换按钮
  • 存储记录不清空:点击存储记录后,能量和食用量输入框保留内容,方便下次添加
  • 统一保存入口:移除标签面板底部的"保存标签"表单,只保留主面板的"存为标签"按钮,更简洁


[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes" />
    <title>热量计算+体重记录</title>
    <style>
        /* ===== CSS变量(主题色) ===== */
        :root {
            --primary-start: #ff7e5f;
            --primary-end: #feb47b;
            --primary-shadow: rgba(255, 126, 95, 0.25);
            --active-bg: #ff7e5f;
            --active-color: #fff;
        }
        /* 主题色方案 12种 */
        .theme-1 {
            --primary-start: #ff7e5f;
            --primary-end: #feb47b;
            --primary-shadow: rgba(255, 126, 95, 0.25);
            --active-bg: #ff7e5f;
        }
        .theme-2 {
            --primary-start: #11998e;
            --primary-end: #38ef7d;
            --primary-shadow: rgba(17, 153, 142, 0.25);
            --active-bg: #11998e;
        }
        .theme-3 {
            --primary-start: #2193b0;
            --primary-end: #6dd5ed;
            --primary-shadow: rgba(33, 147, 176, 0.25);
            --active-bg: #2193b0;
        }
        .theme-4 {
            --primary-start: #8E2DE2;
            --primary-end: #4A00E0;
            --primary-shadow: rgba(142, 45, 226, 0.25);
            --active-bg: #8E2DE2;
        }
        .theme-5 {
            --primary-start: #f093fb;
            --primary-end: #f5576c;
            --primary-shadow: rgba(245, 87, 108, 0.25);
            --active-bg: #f5576c;
        }
        .theme-6 {
            --primary-start: #a8e063;
            --primary-end: #56ab2f;
            --primary-shadow: rgba(86, 171, 47, 0.25);
            --active-bg: #56ab2f;
        }
        .theme-7 {
            --primary-start: #f2994a;
            --primary-end: #f2c94c;
            --primary-shadow: rgba(242, 153, 74, 0.25);
            --active-bg: #f2994a;
        }
        .theme-8 {
            --primary-start: #00c6fb;
            --primary-end: #005bea;
            --primary-shadow: rgba(0, 198, 251, 0.25);
            --active-bg: #00c6fb;
        }
        .theme-9 {
            --primary-start: #cb2d3e;
            --primary-end: #ef473a;
            --primary-shadow: rgba(203, 45, 62, 0.25);
            --active-bg: #cb2d3e;
        }
        .theme-10 {
            --primary-start: #00b09b;
            --primary-end: #96c93d;
            --primary-shadow: rgba(0, 176, 155, 0.25);
            --active-bg: #00b09b;
        }
        .theme-11 {
            --primary-start: #1a2a6c;
            --primary-end: #b21f1f;
            --primary-shadow: rgba(26, 42, 108, 0.25);
            --active-bg: #1a2a6c;
        }
        .theme-12 {
            --primary-start: #2c3e50;
            --primary-end: #3498db;
            --primary-shadow: rgba(44, 62, 80, 0.25);
            --active-bg: #2c3e50;
        }

        /* ===== 全局 ===== */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            -webkit-tap-highlight-color: transparent;
        }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
            background: #f2f4f8;
            min-height: 100vh;
            padding: 8px 10px 100px;
            color: #1a1a2e;
            overflow-x: hidden;
        }
        .app {
            max-width: 500px;
            margin: 0 auto;
            position: relative;
        }
        .card {
            background: #fff;
            border-radius: 18px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
            padding: 14px 14px;
            margin-bottom: 12px;
            transition: box-shadow 0.2s;
        }

        /* ===== 顶部栏 ===== */
        .top-bar {
            display: flex;
            align-items: center;
            justify-content: space-between;
            background: linear-gradient(135deg, var(--primary-start), var(--primary-end));
            border-radius: 18px;
            padding: 8px 16px;
            color: #fff;
            margin-bottom: 10px;
            min-height: 48px;
            transition: background 0.4s, box-shadow 0.4s;
            box-shadow: 0 4px 16px var(--primary-shadow);
        }
        .top-bar .brand {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .top-bar .brand .icon {
            font-size: 26px;
            line-height: 1;
        }
        .top-bar .brand .title {
            font-size: 18px;
            font-weight: 700;
            letter-spacing: 0.3px;
        }
        .top-bar .actions {
            display: flex;
            gap: 6px;
            align-items: center;
        }
        .top-bar .actions button {
            background: rgba(255, 255, 255, 0.20);
            border: none;
            border-radius: 30px;
            padding: 4px 12px;
            font-size: 15px;
            color: #fff;
            cursor: pointer;
            transition: background 0.2s, transform 0.1s;
            backdrop-filter: blur(2px);
            line-height: 1.4;
            white-space: nowrap;
            font-weight: 500;
        }
        .top-bar .actions button:active {
            background: rgba(255, 255, 255, 0.35);
            transform: scale(0.95);
        }

        /* ===== 日期+餐次+输入 合并卡片 ===== */
        .date-meal-row {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 6px 12px;
            margin-bottom: 12px;
            padding-bottom: 10px;
            border-bottom: 1px solid #f0f2f7;
        }
        .date-meal-row .date-left {
            display: flex;
            align-items: center;
            gap: 6px;
            font-size: 15px;
            font-weight: 600;
            color: #1a1a2e;
            flex: 1;
            white-space: nowrap;
        }
        .date-meal-row .date-left .date-icon {
            font-size: 18px;
        }
        .date-meal-row .meal-right-group {
            display: flex;
            align-items: center;
            gap: 6px;
        }
        .date-meal-row .clear-btn {
            width: 28px;
            height: 28px;
            border-radius: 50%;
            border: none;
            background: #f0f2f7;
            font-size: 14px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: background 0.2s, transform 0.1s;
            padding: 0;
        }
        .date-meal-row .clear-btn:active {
            transform: scale(0.9);
            background: #e4e7f0;
        }
        .date-meal-row .meal-select {
            padding: 4px 10px;
            border-radius: 20px;
            border: none;
            background: var(--active-bg);
            color: var(--active-color);
            font-size: 12px;
            font-weight: 500;
            cursor: pointer;
            box-shadow: 0 2px 8px var(--primary-shadow);
            appearance: none;
            -webkit-appearance: none;
            background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right 8px center;
            padding-right: 24px;
        }
        .date-meal-row .meal-select:focus {
            outline: none;
        }

        /* 输入区 */
        .input-row-wrap {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 8px;
            flex-wrap: nowrap;
        }
        .input-row-wrap:last-child {
            margin-bottom: 0;
        }
        .input-field {
            display: flex;
            align-items: center;
            gap: 4px;
            background: #f7f8fc;
            border-radius: 10px;
            border: 2px solid transparent;
            transition: border-color 0.25s, background 0.25s, box-shadow 0.25s;
            padding: 0 6px 0 10px;
            flex-shrink: 0;
        }
        .input-field:focus-within {
            border-color: var(--active-bg);
            background: #fff;
            box-shadow: 0 0 0 4px rgba(255, 126, 95, 0.10);
        }
        .input-field input[type="text"],
        .input-field input[type="number"] {
            border: none;
            outline: none;
            background: transparent;
            font-size: 15px;
            font-weight: 500;
            color: #1a1a2e;
            padding: 8px 2px 8px 0;
            width: 100%;
            min-width: 20px;
            -moz-appearance: textfield;
        }
        .input-field input[type="number"]::-webkit-outer-spin-button,
        .input-field input[type="number"]::-webkit-inner-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }
        .input-field input::placeholder {
            color: #b0b8c4;
            font-weight: 400;
            font-size: 13px;
        }
        .input-field .unit-text {
            font-size: 13px;
            font-weight: 500;
            color: #555;
            white-space: nowrap;
            flex-shrink: 0;
            padding-right: 2px;
        }
        .input-field select {
            padding: 6px 2px 6px 0;
            font-size: 13px;
            font-weight: 500;
            color: #2d3436;
            background: transparent;
            border: none;
            outline: none;
            cursor: pointer;
            flex-shrink: 0;
            min-width: 32px;
            text-align: right;
            appearance: none;
            -webkit-appearance: none;
            background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%23999'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right 0 center;
            padding-right: 14px;
        }
        .input-field select option {
            font-weight: 400;
        }

        /* 各字段宽度 */
        .field-name {
            flex: 2 1 100px;
            min-width: 60px;
        }
        .field-perunit {
            flex: 1 1 80px;
            min-width: 70px;
        }
        .field-energy {
            flex: 1 1 60px;
            min-width: 50px;
        }
        .field-serving {
            flex: 1 1 80px;
            min-width: 70px;
        }
        .field-serving .input-field {
            flex: 1;
        }
        .field-serving .input-field select {
            min-width: 30px;
            padding-right: 12px;
            font-size: 12px;
        }

        /* 按钮 */
        .btn-save-tag,
        .btn-save-record {
            flex-shrink: 0;
            border: none;
            border-radius: 30px;
            padding: 6px 14px;
            font-size: 14px;
            font-weight: 600;
            color: #fff;
            background: var(--active-bg);
            cursor: pointer;
            transition: background 0.2s, transform 0.1s;
            white-space: nowrap;
            box-shadow: 0 2px 8px var(--primary-shadow);
            line-height: 1.4;
        }
        .btn-save-tag:active,
        .btn-save-record:active {
            transform: scale(0.95);
        }
        .btn-save-tag {
            background: rgba(0, 0, 0, 0.08);
            color: #555;
            box-shadow: none;
        }
        .btn-save-tag:active {
            background: rgba(0, 0, 0, 0.15);
        }

        /* ===== 结果区 三列 ===== */
        .result-grid {
            display: grid;
            grid-template-columns: 1fr 1fr 1fr;
            gap: 8px;
            align-items: stretch;
        }
        .result-item {
            background: #f7f8fc;
            border-radius: 12px;
            padding: 10px 6px;
            text-align: center;
            display: flex;
            flex-direction: column;
            justify-content: center;
            min-height: 60px;
        }
        .result-item .rlabel {
            font-size: 12px;
            font-weight: 500;
            color: #888;
        }
        .result-item .rvalue {
            font-size: 24px;
            font-weight: 700;
            color: #1a1a2e;
            line-height: 1.2;
        }
        .result-item .rvalue .runit {
            font-size: 14px;
            font-weight: 400;
            color: #999;
        }
        .result-item.highlight-green .rvalue {
            color: #00b894;
        }
        .result-item.highlight-yellow .rvalue {
            color: #f9ca24;
        }
        .result-item.highlight-red .rvalue {
            color: #e85d3a;
        }
        .result-item.target-item .rvalue {
            color: #2d7dd2;
        }

        /* ===== 差值提示(在结果区下方) ===== */
        .diff-hint {
            margin-top: 10px;
            padding-top: 10px;
            border-top: 1px solid #f0f2f7;
            text-align: center;
            font-size: 14px;
            font-weight: 500;
            color: #1a1a2e;
            min-height: 24px;
        }
        .diff-hint .diff-value {
            font-weight: 700;
        }
        .diff-hint.success .diff-value {
            color: #00b894;
        }
        .diff-hint.warning .diff-value {
            color: #f9ca24;
        }
        .diff-hint.danger .diff-value {
            color: #e85d3a;
        }

        /* ===== 日历卡片 ===== */
        .calendar-wrap {
            padding-bottom: 6px;
        }
        .calendar-nav {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 6px;
        }
        .calendar-nav .cal-month {
            font-size: 16px;
            font-weight: 600;
            color: #1a1a2e;
        }
        .calendar-nav .cal-btn {
            background: #f0f2f7;
            border: none;
            border-radius: 30px;
            padding: 2px 14px;
            font-size: 14px;
            font-weight: 500;
            color: #555;
            cursor: pointer;
            transition: background 0.2s;
        }
        .calendar-nav .cal-btn:active {
            background: #e0e2e7;
        }

        /* 日历网格 - 紧凑长方形 */
        .calendar-grid {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 3px;
            text-align: center;
        }
        .calendar-grid .cal-weekday {
            font-size: 11px;
            font-weight: 500;
            color: #999;
            padding: 2px 0;
        }
        .calendar-grid .cal-day {
            display: flex;
            flex-direction: column;
            justify-content: flex-start;
            align-items: center;
            border-radius: 8px;
            background: #f7f8fc;
            font-weight: 500;
            color: #1a1a2e;
            cursor: pointer;
            transition: background 0.15s, transform 0.1s;
            padding: 4px 2px 3px;
            min-height: 44px;
            border: 2px solid transparent;
            line-height: 1.2;
            position: relative;
        }
        .calendar-grid .cal-day:active {
            transform: scale(0.94);
        }
        .calendar-grid .cal-day .day-number {
            font-size: 15px;
            font-weight: 600;
            line-height: 1.2;
            margin-bottom: 1px;
        }
        .calendar-grid .cal-day .day-kcal {
            font-size: 8px;
            font-weight: 700;
            color: #e85d3a;
            line-height: 1.2;
            max-width: 100%;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .calendar-grid .cal-day .day-weight {
            font-size: 8px;
            font-weight: 700;
            color: #2d7dd2;
            line-height: 1.2;
            max-width: 100%;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .calendar-grid .cal-day.status-green {
            background: #e6f9ed;
            border-color: #4dd4b0;
        }
        .calendar-grid .cal-day.status-yellow {
            background: #fff8e1;
            border-color: #f9ca24;
        }
        .calendar-grid .cal-day.status-red {
            background: #fde8e8;
            border-color: #e85d3a;
        }
        .calendar-grid .cal-day.today {
            border-color: var(--active-bg);
            border-width: 2px;
        }
        .calendar-grid .cal-day.other-month {
            opacity: 0.3;
        }
        .calendar-grid .cal-day.selected {
            box-shadow: 0 0 0 2px var(--active-bg);
        }

        /* ===== 饮食记录卡片(独立) ===== */
        .record-card {
            display: none;
            margin-top: 0;
        }
        .record-card.show {
            display: block;
        }
        .record-card .dd-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 6px;
        }
        .record-card .dd-title {
            font-size: 14px;
            font-weight: 600;
            color: #1a1a2e;
        }
        .record-card .dd-title .dd-total-kcal {
            color: #e85d3a;
            font-weight: 600;
        }
        .record-card .dd-weight {
            font-size: 13px;
            font-weight: 500;
            color: #2d7dd2;
            white-space: nowrap;
        }
        .record-card .dd-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 6px 8px;
            font-size: 14px;
            border-bottom: 1px solid #edf0f5;
            background: #fafbfc;
            border-radius: 6px;
            margin-bottom: 4px;
        }
        .record-card .dd-item:last-child {
            border-bottom: none;
            margin-bottom: 0;
        }
        .record-card .dd-item .dd-name {
            color: #1a1a2e;
            font-weight: 500;
        }
        .record-card .dd-item .dd-kcal {
            font-weight: 500;
            color: #e85d3a;
            margin-left: 8px;
        }
        .record-card .dd-item .dd-del {
            background: none;
            border: none;
            color: #ccc;
            font-size: 16px;
            cursor: pointer;
            padding: 0 4px;
            flex-shrink: 0;
            margin-left: 8px;
            transition: color 0.2s;
        }
        .record-card .dd-item .dd-del:active {
            color: #e85d3a;
        }
        .record-card .dd-empty {
            color: #b0b8c4;
            font-size: 13px;
            text-align: center;
            padding: 16px 0;
        }
        .record-card .meal-group-label {
            font-weight: 600;
            font-size: 13px;
            color: #888;
            margin: 6px 0 4px 4px;
        }

        /* ===== 设置弹窗 ===== */
        .modal-overlay {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.40);
            z-index: 2000;
            display: none;
            align-items: center;
            justify-content: center;
            backdrop-filter: blur(3px);
            padding: 20px;
        }
        .modal-overlay.show {
            display: flex;
        }
        .modal-box {
            background: #fff;
            border-radius: 24px;
            max-width: 420px;
            width: 100%;
            padding: 20px 18px 18px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.20);
            max-height: 90vh;
            overflow-y: auto;
        }
        .modal-box .modal-title {
            font-size: 18px;
            font-weight: 700;
            color: #1a1a2e;
            margin-bottom: 14px;
            text-align: center;
        }
        .settings-grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px 12px;
        }
        .settings-grid .field {
            margin-bottom: 0;
        }
        .settings-grid .field label {
            display: block;
            font-size: 12px;
            font-weight: 500;
            color: #2d3436;
            margin-bottom: 2px;
        }
        .settings-grid .field input,
        .settings-grid .field select {
            width: 100%;
            padding: 7px 10px;
            border: 1px solid #e0e4ec;
            border-radius: 8px;
            font-size: 14px;
            background: #f7f8fc;
            outline: none;
            transition: border-color 0.2s;
        }
        .settings-grid .field input:focus,
        .settings-grid .field select:focus {
            border-color: var(--active-bg);
            background: #fff;
        }
        .settings-grid .field select {
            appearance: auto;
        }
        .settings-target-full {
            grid-column: 1 / -1;
            display: flex;
            gap: 10px;
            margin-top: 2px;
        }
        .settings-target-full button {
            flex: 1;
            padding: 7px 0;
            border: 2px solid #e0e4ec;
            border-radius: 8px;
            background: #f7f8fc;
            font-size: 14px;
            font-weight: 500;
            color: #555;
            cursor: pointer;
            transition: all 0.2s;
        }
        .settings-target-full button.active {
            border-color: var(--active-bg);
            background: #fff5f0;
            color: var(--active-bg);
        }
        .settings-target-full button:active {
            transform: scale(0.96);
        }
        .settings-result {
            grid-column: 1 / -1;
            margin-top: 6px;
            padding: 8px 10px;
            background: #f0f7ff;
            border-radius: 8px;
            font-size: 13px;
            color: #555;
            line-height: 1.6;
        }
        .settings-result strong {
            color: #1a1a2e;
        }
        .modal-actions {
            display: flex;
            gap: 10px;
            margin-top: 14px;
        }
        .modal-actions button {
            flex: 1;
            padding: 9px 0;
            border: none;
            border-radius: 30px;
            font-size: 15px;
            font-weight: 600;
            cursor: pointer;
            transition: background 0.2s;
        }
        .modal-actions .btn-save {
            background: var(--active-bg);
            color: #fff;
        }
        .modal-actions .btn-save:active {
            opacity: 0.8;
        }
        .modal-actions .btn-close {
            background: #f0f2f7;
            color: #555;
        }
        .modal-actions .btn-close:active {
            background: #e0e2e7;
        }
        .modal-actions .btn-dark-toggle {
            flex: 1;
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: #fff;
        }
        .modal-actions .btn-dark-toggle:active {
            opacity: 0.85;
        }

        /* ===== 主题选择弹窗 ===== */
        .theme-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 10px;
            margin: 12px 0;
        }
        .theme-btn {
            padding: 12px 0;
            border: 3px solid #e0e4ec;
            border-radius: 12px;
            cursor: pointer;
            transition: border-color 0.2s, transform 0.1s;
            background: #f7f8fc;
            font-size: 12px;
            font-weight: 500;
            color: #555;
            text-align: center;
        }
        .theme-btn:active {
            transform: scale(0.94);
        }
        .theme-btn.active {
            border-color: var(--active-bg);
            background: #fff5f0;
        }
        .theme-btn .color-preview {
            width: 100%;
            height: 20px;
            border-radius: 6px;
            margin-bottom: 4px;
        }

        /* ===== 备份弹窗 ===== */
        .backup-section {
            margin-bottom: 12px;
        }
        .backup-section-title {
            font-size: 13px;
            font-weight: 600;
            color: #555;
            margin-bottom: 8px;
        }
        .backup-checkboxes {
            display: flex;
            flex-wrap: wrap;
            gap: 6px 12px;
        }
        .backup-checkbox-item {
            display: flex;
            align-items: center;
            gap: 6px;
            font-size: 13px;
            color: #333;
            cursor: pointer;
            flex: 1 1 calc(50% - 6px);
            min-width: 0;
            user-select: none;
        }
        .backup-checkbox-item.backup-full-row {
            flex: 1 1 100%;
        }
        .backup-checkbox-item input[type="checkbox"] {
            width: 16px;
            height: 16px;
            cursor: pointer;
            accent-color: var(--active-bg);
            flex-shrink: 0;
        }
        .backup-checkbox-item span {
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .backup-tip {
            font-size: 12px;
            color: #999;
            text-align: center;
            margin-bottom: 4px;
        }

        /* ===== 体重弹窗 ===== */
        .weight-modal .modal-box {
            max-width: 460px;
        }
        .weight-modal .compact-row {
            display: flex;
            align-items: center;
            gap: 8px;
            flex-wrap: nowrap;
            margin-bottom: 10px;
        }
        .weight-modal .compact-row .field-label {
            font-size: 14px;
            font-weight: 500;
            color: #2d3436;
            white-space: nowrap;
            margin-right: 2px;
        }
        .weight-modal .compact-row input {
            flex: 1;
            min-width: 50px;
            padding: 8px 8px;
            border: 1px solid #e0e4ec;
            border-radius: 8px;
            font-size: 15px;
            background: #f7f8fc;
            outline: none;
            transition: border-color 0.2s;
            width: 100%;
        }
        .weight-modal .compact-row input:focus {
            border-color: var(--active-bg);
            background: #fff;
        }
        .weight-modal .compact-row select {
            flex-shrink: 0;
            padding: 8px 4px 8px 8px;
            border: 1px solid #e0e4ec;
            border-radius: 8px;
            font-size: 14px;
            background: #f7f8fc;
            outline: none;
            cursor: pointer;
            appearance: auto;
        }
        .weight-modal .compact-row select:focus {
            border-color: var(--active-bg);
        }
        .weight-modal .action-row {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-top: 10px;
        }
        .weight-modal .action-row .weight-jin {
            font-size: 14px;
            color: #888;
            flex-shrink: 0;
            min-width: 60px;
            text-align: left;
        }
        .weight-modal .action-row .modal-actions {
            flex: 1;
            margin-top: 0;
            gap: 8px;
        }
        .weight-modal .action-row .modal-actions button {
            padding: 8px 0;
            font-size: 14px;
        }
        .weight-modal .chart-container {
            margin-top: 14px;
            border-top: 1px solid #f0f2f7;
            padding-top: 12px;
        }
        .weight-modal .chart-container .chart-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
            flex-wrap: wrap;
            gap: 4px;
        }
        .weight-modal .chart-container .chart-header .chart-title {
            font-size: 14px;
            font-weight: 600;
            color: #1a1a2e;
        }
        .weight-modal .chart-container .chart-header .period-group {
            display: flex;
            gap: 3px;
            flex-wrap: wrap;
        }
        .weight-modal .chart-container .chart-header .period-group button {
            padding: 2px 8px;
            border: 1px solid #e0e4ec;
            border-radius: 30px;
            background: #f7f8fc;
            font-size: 11px;
            font-weight: 500;
            color: #555;
            cursor: pointer;
            transition: all 0.2s;
        }
        .weight-modal .chart-container .chart-header .period-group button.active {
            background: var(--active-bg);
            color: #fff;
            border-color: var(--active-bg);
        }
        .weight-modal .chart-container .chart-header .period-group button:active {
            transform: scale(0.94);
        }
        .weight-modal .chart-container canvas {
            width: 100%;
            height: 180px;
            background: #fafbfc;
            border-radius: 10px;
            display: block;
        }
        .weight-modal .chart-container .no-data {
            text-align: center;
            padding: 30px 0;
            color: #b0b8c4;
            font-size: 14px;
        }

        /* ===== 删除确认框 ===== */
        .modal-confirm {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #fff;
            border-radius: 20px;
            padding: 20px 24px 18px;
            z-index: 1002;
            max-width: 300px;
            width: 90%;
            box-shadow: 0 16px 60px rgba(0, 0, 0, 0.18);
            display: none;
            text-align: center;
        }
        .modal-confirm.show {
            display: block;
        }
        .modal-confirm .mc-title {
            font-size: 17px;
            font-weight: 600;
            color: #1a1a2e;
            margin-bottom: 4px;
        }
        .modal-confirm .mc-desc {
            font-size: 13px;
            color: #666;
            margin-bottom: 14px;
        }
        .modal-confirm .mc-btns {
            display: flex;
            gap: 10px;
            justify-content: center;
        }
        .modal-confirm .mc-btns button {
            padding: 6px 20px;
            border: none;
            border-radius: 30px;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: background 0.2s;
        }
        .modal-confirm .mc-btns .mc-cancel {
            background: #f0f2f7;
            color: #555;
        }
        .modal-confirm .mc-btns .mc-confirm {
            background: #e85d3a;
            color: #fff;
        }
        .modal-confirm .mc-btns .mc-confirm:active {
            background: #d14a2a;
        }
        .modal-confirm .mc-btns .mc-cancel:active {
            background: #e0e2e7;
        }

        /* ===== Toast 提示 ===== */
        .toast-container {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            z-index: 2000;
            pointer-events: none;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 8px;
        }
        .toast-item {
            background: rgba(0, 0, 0, 0.75);
            color: #fff;
            padding: 10px 20px;
            border-radius: 24px;
            font-size: 14px;
            font-weight: 500;
            max-width: 80vw;
            text-align: center;
            opacity: 0;
            transform: translateY(10px);
            transition: opacity 0.3s ease, transform 0.3s ease;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .toast-item.show {
            opacity: 1;
            transform: translateY(0);
        }
        .toast-item.success {
            background: rgba(0, 184, 148, 0.9);
        }
        .toast-item.error {
            background: rgba(232, 93, 58, 0.9);
        }
        .toast-item.warning {
            background: rgba(249, 202, 36, 0.9);
            color: #333;
        }

        /* ===== 悬浮标签 ===== */
        .tag-fab {
            position: fixed;
            right: 12px;
            bottom: 120px;
            z-index: 1000;
            display: flex;
            flex-direction: column;
            align-items: flex-end;
        }
        .tag-fab .fab-btn {
            width: 52px;
            height: 52px;
            border-radius: 50%;
            background: linear-gradient(135deg, var(--primary-start), var(--primary-end));
            border: none;
            color: #fff;
            font-size: 14px;
            font-weight: 600;
            box-shadow: 0 4px 16px var(--primary-shadow);
            cursor: pointer;
            transition: transform 0.2s, box-shadow 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
            letter-spacing: 0.5px;
        }
        .tag-fab .fab-btn:active {
            transform: scale(0.90);
        }
        .tag-fab .fab-btn .fab-icon {
            font-size: 22px;
            line-height: 1;
        }

        /* ===== 标签面板 ===== */
        .tag-panel {
            position: fixed;
            right: -340px;
            top: 0;
            bottom: 0;
            width: 320px;
            max-width: 85vw;
            background: #fff;
            box-shadow: -4px 0 30px rgba(0, 0, 0, 0.12);
            z-index: 1001;
            transition: right 0.30s cubic-bezier(0.22, 1, 0.36, 1);
            display: flex;
            flex-direction: column;
            border-radius: 20px 0 0 20px;
            overflow: hidden;
        }
        .tag-panel.open {
            right: 0;
        }
        .tag-panel .panel-header {
            padding: 14px 16px 10px;
            border-bottom: 1px solid #f0f2f7;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-shrink: 0;
        }
        .tag-panel .panel-header .ph-title {
            font-size: 17px;
            font-weight: 700;
            color: #1a1a2e;
        }
        .tag-panel .panel-header .ph-close {
            background: none;
            border: none;
            font-size: 22px;
            cursor: pointer;
            color: #999;
            padding: 0 4px;
        }
        .tag-panel .panel-body {
            flex: 1;
            overflow-y: auto;
            padding: 10px 12px 16px;
            -webkit-overflow-scrolling: touch;
            touch-action: pan-y;
        }
        .tag-panel .panel-body .tag-item {
            display: flex;
            align-items: center;
            gap: 6px;
            padding: 10px 12px;
            border-radius: 8px;
            background: #f7f8fc;
            margin-bottom: 4px;
            cursor: pointer;
            transition: background 0.15s, transform 0.1s;
            user-select: none;
            position: relative;
            min-height: 44px;
        }
        .tag-panel .panel-body .tag-item:active {
            transform: scale(0.97);
            background: #eef0f7;
        }
        .tag-panel .panel-body .tag-item.tag-selected {
            background: var(--active-bg);
        }
        .tag-panel .panel-body .tag-item.tag-selected .tag-name {
            color: #fff;
        }
        .tag-panel .panel-body .tag-item.tag-selected span:nth-child(2) {
            color: rgba(255,255,255,0.8) !important;
        }
        .tag-panel .panel-body .tag-item.tag-selected .tag-del {
            color: rgba(255,255,255,0.7);
        }
        .tag-panel .panel-body .tag-item .tag-name {
            flex: 1;
            font-size: 15px;
            font-weight: 500;
            color: #1a1a2e;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            pointer-events: none;
        }
        .tag-panel .panel-body .tag-item .tag-del {
            background: none;
            border: none;
            color: #ccc;
            font-size: 16px;
            cursor: pointer;
            padding: 0 4px;
            flex-shrink: 0;
            pointer-events: auto;
        }
        .tag-panel .panel-body .tag-item .tag-del:active {
            color: #e85d3a;
        }
        .tag-panel .panel-body .tag-add-area {
            margin-top: 10px;
            padding: 10px 12px;
            background: #f7f8fc;
            border-radius: 10px;
            border: 2px dashed #dce0e8;
        }
        .tag-panel .panel-body .tag-add-area .ta-row {
            display: flex;
            gap: 4px;
            margin-bottom: 4px;
            flex-wrap: wrap;
        }
        .tag-panel .panel-body .tag-add-area .ta-row input,
        .tag-panel .panel-body .tag-add-area .ta-row select {
            flex: 1;
            min-width: 50px;
            padding: 6px 8px;
            border: 1px solid #e0e4ec;
            border-radius: 6px;
            font-size: 13px;
            background: #fff;
            outline: none;
            transition: border-color 0.2s;
        }
        .tag-panel .panel-body .tag-add-area .ta-row input:focus,
        .tag-panel .panel-body .tag-add-area .ta-row select:focus {
            border-color: var(--active-bg);
        }
        .tag-panel .panel-body .tag-add-area .ta-row input::placeholder {
            color: #b0b8c4;
        }
        .tag-panel .panel-body .tag-add-area .ta-row .ta-btn {
            padding: 6px 14px;
            border: none;
            border-radius: 6px;
            background: var(--active-bg);
            color: #fff;
            font-weight: 600;
            font-size: 13px;
            cursor: pointer;
            transition: background 0.2s;
        }
        .tag-panel .panel-body .tag-add-area .ta-row .ta-btn:active {
            opacity: 0.8;
        }
        .tag-panel .panel-body .tag-add-area .ta-msg {
            font-size: 12px;
            color: #e85d3a;
            min-height: 18px;
            padding-left: 4px;
        }
        .tag-panel .panel-body .tag-add-area .ta-msg.success {
            color: #00b894;
        }
        .tag-panel .panel-body .tag-count {
            font-size: 12px;
            color: #999;
            text-align: right;
            padding: 2px 4px 6px;
        }
        .overlay-tag {
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.25);
            z-index: 1000;
            display: none;
            backdrop-filter: blur(2px);
        }
        .overlay-tag.show {
            display: block;
        }

        /* ===== 暗色模式适配 ===== */
        /* 手动切换暗黑模式 */
            body.dark-mode {
                background: #12121a;
                color: #e8e8f0;
            }
            body.dark-mode .card {
                background: #1e1e2a;
                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
            }
            body.dark-mode .date-meal-row {
                background: transparent;
                border-bottom-color: #2a2a3a;
            }
            body.dark-mode .date-meal-row .date-left {
                color: #e8e8f0;
            }
            body.dark-mode .date-meal-row .meal-group .meal-btn {
                background: #2a2a38;
                color: #aaa;
            }
            body.dark-mode .date-meal-row .meal-group .meal-btn.active {
                background: var(--active-bg);
                color: #fff;
            }
            body.dark-mode .input-field {
                background: #2a2a38;
            }
            body.dark-mode .input-field input[type="text"],
            body.dark-mode .input-field input[type="number"] {
                color: #e8e8f0;
            }
            body.dark-mode .input-field input::placeholder {
                color: #6a6a7a;
            }
            body.dark-mode .input-field .unit-text {
                color: #aaa;
            }
            body.dark-mode .input-field select {
                color: #e8e8f0;
            }
            body.dark-mode .input-field select option {
                background: #2a2a38;
                color: #e8e8f0;
            }
            body.dark-mode .btn-save-tag {
                color: #ccc;
                background: rgba(255, 255, 255, 0.08);
            }
            body.dark-mode .result-item {
                background: #2a2a38;
            }
            body.dark-mode .result-item .rlabel {
                color: #7a7a8a;
            }
            body.dark-mode .result-item .rvalue {
                color: #e8e8f0;
            }
            body.dark-mode .result-item.highlight-green .rvalue {
                color: #4dd4b0;
            }
            body.dark-mode .result-item.highlight-yellow .rvalue {
                color: #f9ca24;
            }
            body.dark-mode .result-item.highlight-red .rvalue {
                color: #ff6a4a;
            }
            body.dark-mode .result-item.target-item .rvalue {
                color: #5b9bd5;
            }
            body.dark-mode .diff-hint {
                border-top-color: #2a2a3a;
                color: #e8e8f0;
            }
            body.dark-mode .diff-hint.success .diff-value {
                color: #4dd4b0;
            }
            body.dark-mode .diff-hint.warning .diff-value {
                color: #f9ca24;
            }
            body.dark-mode .diff-hint.danger .diff-value {
                color: #ff6a4a;
            }
            body.dark-mode .calendar-grid .cal-day {
                background: #2a2a38;
                color: #e8e8f0;
            }
            body.dark-mode .calendar-grid .cal-day .day-kcal {
                color: #ff6a4a;
            }
            body.dark-mode .calendar-grid .cal-day.status-green {
                background: #1a2a26;
                border-color: #4dd4b0;
            }
            body.dark-mode .calendar-grid .cal-day.status-yellow {
                background: #2a2a1a;
                border-color: #f9ca24;
            }
            body.dark-mode .calendar-grid .cal-day.status-red {
                background: #2a1a1a;
                border-color: #ff6a4a;
            }
            body.dark-mode .calendar-grid .cal-day.today {
                border-color: var(--active-bg);
            }
            body.dark-mode .calendar-grid .cal-day.selected {
                box-shadow: 0 0 0 2px var(--active-bg);
            }
            body.dark-mode .calendar-grid .cal-day.other-month {
                opacity: 0.2;
            }
            body.dark-mode .calendar-grid .cal-day .day-weight {
                color: #5b9bd5;
            }
            body.dark-mode .calendar-nav .cal-btn {
                background: #2a2a38;
                color: #ccc;
            }
            body.dark-mode .record-card .dd-item {
                background: #2a2a38;
                border-bottom-color: #3a3a4a;
            }
            body.dark-mode .record-card .dd-item .dd-name {
                color: #e8e8f0;
            }
            body.dark-mode .record-card .dd-weight {
                color: #5b9bd5;
            }
            body.dark-mode .modal-box {
                background: #1e1e2a;
            }
            body.dark-mode .modal-box .modal-title {
                color: #e8e8f0;
            }
            body.dark-mode .settings-grid .field label {
                color: #d0d0e0;
            }
            body.dark-mode .settings-grid .field input,
            body.dark-mode .settings-grid .field select {
                background: #2a2a38;
                border-color: #3a3a4a;
                color: #e8e8f0;
            }
            body.dark-mode .settings-grid .field input:focus,
            body.dark-mode .settings-grid .field select:focus {
                border-color: var(--active-bg);
                background: #1e1e2a;
            }
            body.dark-mode .settings-target-full button {
                background: #2a2a38;
                border-color: #3a3a4a;
                color: #aaa;
            }
            body.dark-mode .settings-target-full button.active {
                border-color: var(--active-bg);
                background: #2a1e1a;
                color: var(--active-bg);
            }
            body.dark-mode .settings-result {
                background: #1a2a3a;
                color: #aaa;
            }
            body.dark-mode .settings-result strong {
                color: #e8e8f0;
            }
            body.dark-mode .modal-actions .btn-save {
                background: var(--active-bg);
            }
            body.dark-mode .modal-actions .btn-close {
                background: #2a2a38;
                color: #ccc;
            }
            body.dark-mode .modal-confirm {
                background: #1e1e2a;
            }
            body.dark-mode .modal-confirm .mc-title {
                color: #e8e8f0;
            }
            body.dark-mode .modal-confirm .mc-desc {
                color: #aaa;
            }
            body.dark-mode .modal-confirm .mc-btns .mc-cancel {
                background: #2a2a38;
                color: #ccc;
            }
            body.dark-mode .backup-section-title {
                color: #aaa;
            }
            body.dark-mode .backup-checkbox-item {
                color: #d0d0e0;
            }
            body.dark-mode .backup-tip {
                color: #7a7a8a;
            }
            body.dark-mode .tag-panel {
                background: #1e1e2a;
            }
            body.dark-mode .tag-panel .panel-header {
                border-bottom-color: #2a2a3a;
            }
            body.dark-mode .tag-panel .panel-header .ph-title {
                color: #e8e8f0;
            }
            body.dark-mode .tag-panel .panel-body .tag-item {
                background: #2a2a38;
            }
            body.dark-mode .tag-panel .panel-body .tag-item:active {
                background: #3a3a4a;
            }
            body.dark-mode .tag-panel .panel-body .tag-item.tag-selected {
                background: var(--active-bg);
            }
            body.dark-mode .tag-panel .panel-body .tag-item.tag-selected .tag-name {
                color: #fff;
            }
            body.dark-mode .tag-panel .panel-body .tag-item .tag-name {
                color: #e8e8f0;
            }
            body.dark-mode .tag-panel .panel-body .tag-add-area {
                background: #2a2a38;
                border-color: #3a3a4a;
            }
            body.dark-mode .tag-panel .panel-body .tag-add-area .ta-row input,
            body.dark-mode .tag-panel .panel-body .tag-add-area .ta-row select {
                background: #1e1e2a;
                border-color: #3a3a4a;
                color: #e8e8f0;
            }
            body.dark-mode .tag-panel .panel-body .tag-add-area .ta-row input::placeholder {
                color: #6a6a7a;
            }
            body.dark-mode .tag-panel .panel-body .tag-count {
                color: #6a6a7a;
            }
            body.dark-mode .overlay-tag {
                background: rgba(0, 0, 0, 0.5);
            }
            body.dark-mode .tag-fab .fab-btn {
                background: linear-gradient(135deg, var(--primary-start), var(--primary-end));
            }
            body.dark-mode .weight-modal .chart-container canvas {
                background: #1e1e2a;
            }
            body.dark-mode .weight-modal .chart-container .chart-header .period-group button {
                background: #2a2a38;
                border-color: #3a3a4a;
                color: #aaa;
            }
            body.dark-mode .weight-modal .chart-container .chart-header .period-group button.active {
                background: var(--active-bg);
                border-color: var(--active-bg);
                color: #fff;
            }
            body.dark-mode .weight-modal .compact-row select {
                background: #2a2a38;
                border-color: #3a3a4a;
                color: #e8e8f0;
            }
            body.dark-mode .weight-modal .compact-row input {
                background: #2a2a38;
                border-color: #3a3a4a;
                color: #e8e8f0;
            }
            body.dark-mode .weight-modal .compact-row input:focus {
                border-color: var(--active-bg);
                background: #1e1e2a;
            }
        


        /* ===== 响应式 ===== */
        @media (max-width: 420px) {
            body {
                padding: 6px 6px 80px;
            }
            .card {
                padding: 10px 10px;
                border-radius: 14px;
            }
            .top-bar {
                padding: 6px 12px;
                min-height: 42px;
                border-radius: 14px;
            }
            .top-bar .brand .icon {
                font-size: 22px;
            }
            .top-bar .brand .title {
                font-size: 16px;
            }
            .top-bar .actions button {
                font-size: 13px;
                padding: 3px 8px;
            }
            .date-meal-row .date-left {
                font-size: 13px;
            }
            .date-meal-row .meal-group .meal-btn {
                font-size: 11px;
                padding: 2px 8px;
            }
            .input-row-wrap {
                gap: 6px;
                margin-bottom: 6px;
                flex-wrap: wrap;
            }
            .input-field {
                padding: 0 4px 0 8px;
                border-radius: 8px;
            }
            .input-field input[type="text"],
            .input-field input[type="number"] {
                font-size: 14px;
                padding: 6px 2px 6px 0;
            }
            .input-field .unit-text {
                font-size: 12px;
            }
            .input-field select {
                font-size: 12px;
                padding: 4px 2px 4px 0;
                min-width: 24px;
                padding-right: 12px;
            }
            .field-name {
                flex: 2 1 70px;
                min-width: 50px;
            }
            .field-perunit {
                flex: 1 1 65px;
                min-width: 55px;
            }
            .field-energy {
                flex: 1 1 50px;
                min-width: 45px;
            }
            .field-serving {
                flex: 1 1 70px;
                min-width: 60px;
            }
            .btn-save-tag,
            .btn-save-record {
                font-size: 12px;
                padding: 4px 10px;
            }
            .result-grid {
                grid-template-columns: 1fr 1fr 1fr;
                gap: 4px;
            }
            .result-item {
                padding: 6px 4px;
                min-height: 48px;
            }
            .result-item .rvalue {
                font-size: 20px;
            }
            .diff-hint {
                font-size: 13px;
            }
            .calendar-grid .cal-day {
                min-height: 36px;
                padding: 3px 2px 2px;
            }
            .calendar-grid .cal-day .day-number {
                font-size: 13px;
            }
            .calendar-grid .cal-day .day-kcal {
                font-size: 7px;
            }
            .calendar-grid .cal-day .day-weight {
                font-size: 7px;
            }
            .record-card .dd-item {
                font-size: 13px;
                padding: 4px 6px;
            }
            .tag-panel {
                width: 270px;
                max-width: 80vw;
            }
            .weight-modal .modal-box {
                padding: 16px 12px;
            }
            .weight-modal .compact-row {
                flex-wrap: nowrap;
                gap: 4px;
            }
            .weight-modal .compact-row input {
                min-width: 40px;
                padding: 6px 4px;
                font-size: 14px;
            }
            .weight-modal .compact-row select {
                font-size: 12px;
                padding: 6px 4px;
            }
            .weight-modal .chart-container canvas {
                height: 130px;
            }
            .weight-modal .action-row {
                flex-wrap: nowrap;
                gap: 6px;
            }
            .weight-modal .action-row .weight-jin {
                font-size: 12px;
                min-width: 40px;
            }
            .weight-modal .action-row .modal-actions button {
                font-size: 13px;
                padding: 6px 0;
            }
            .settings-grid {
                grid-template-columns: 1fr 1fr;
                gap: 6px 8px;
            }
            .settings-grid .field label {
                font-size: 11px;
            }
            .settings-grid .field input,
            .settings-grid .field select {
                font-size: 13px;
                padding: 5px 8px;
            }
            .settings-target-full button {
                font-size: 13px;
                padding: 5px 0;
            }
            .settings-result {
                font-size: 12px;
                padding: 6px 8px;
            }
            .modal-box {
                padding: 16px 14px;
            }
            .modal-box .modal-title {
                font-size: 16px;
                margin-bottom: 10px;
            }
            .theme-grid {
                grid-template-columns: repeat(3, 1fr);
                gap: 8px;
            }
        }
        @media (min-width: 769px) {
            body {
                padding: 20px 20px 120px;
                display: flex;
                justify-content: center;
            }
            .app {
                max-width: 540px;
            }
            .tag-fab {
                right: 30px;
                bottom: 140px;
            }
            .tag-panel {
                width: 360px;
                max-width: 30vw;
                border-radius: 24px 0 0 24px;
            }
            .result-item .rvalue {
                font-size: 28px;
            }
            .calendar-grid .cal-day {
                min-height: 48px;
                padding: 4px 2px 3px;
            }
            .calendar-grid .cal-day .day-number {
                font-size: 16px;
            }
            .calendar-grid .cal-day .day-kcal {
                font-size: 9px;
            }
            .calendar-grid .cal-day .day-weight {
                font-size: 9px;
            }
            .weight-modal .chart-container canvas {
                height: 200px;
            }
            .input-field input[type="text"],
            .input-field input[type="number"] {
                font-size: 16px;
                padding: 10px 4px 10px 0;
            }
        }
    </style>
</head>
<body>

    <div class="app" id="app">

        <!-- ===== 顶部栏 ===== -->
        <div class="top-bar" id="topBar">
            <div class="brand">
                <span class="title">热量计算/体重记录</span>
            </div>
            <div class="actions">
                <button id="weightToggle">体重</button>
                <button id="backupToggle">备份</button>
                <button id="settingsToggle">设置</button>
                <button id="themeToggle">主题</button>
            </div>
        </div>

        <!-- ===== 卡片1:日期+餐次+输入 ===== -->
        <div class="card">
            <!-- 日期+餐次 -->
            <div class="date-meal-row">
                <div class="date-left">
                    <span class="date-icon">&#128197;</span>
                    <span id="dateDisplay">--</span>
                </div>
                <div class="meal-right-group">
                    <button class="clear-btn" id="clearInputsBtn" title="清空输入">&#129529;</button>
                    <select id="mealSelect" class="meal-select">
                        <option value="早餐">早餐</option>
                        <option value="中餐">中餐</option>
                        <option value="晚餐">晚餐</option>
                        <option value="加餐">加餐</option>
                    </select>
                </div>
            </div>

            <!-- 输入行1:食物名称 + 每份量 + 存为标签 -->
            <div class="input-row-wrap">
                <div class="field-name">
                    <div class="input-field">
                        <input type="text" id="foodName" placeholder="食物名称" />
                    </div>
                </div>
                <div class="field-perunit">
                    <div class="input-field">
                        <input type="number" id="defaultWeight" value="100" min="0.1" step="1" />
                        <select id="defaultUnit">
                            <option value="g">g</option>
                            <option value="ml">ml</option>
                        </select>
                    </div>
                </div>
                <button class="btn-save-tag" id="saveTagQuick">存为标签</button>
            </div>

            <!-- 输入行2:能量 + 食用量 + 存储记录 -->
            <div class="input-row-wrap">
                <div class="field-energy">
                    <div class="input-field">
                        <input type="number" id="energy" placeholder="能量" min="0" step="1" />
                        <span class="unit-text">kJ</span>
                    </div>
                </div>
                <div class="field-serving">
                    <div class="input-field">
                        <input type="number" id="serving" placeholder="食用量" min="0" step="1" />
                        <select id="servingUnit">
                            <option value="g">g</option>
                            <option value="kg">kg</option>
                            <option value="ml">ml</option>
                        </select>
                    </div>
                </div>
                <button class="btn-save-record" id="saveRecordBtn">存储记录</button>
            </div>
        </div>

        <!-- ===== 卡片2:结果区 三列 + 差值提示 ===== -->
        <div class="card">
            <div class="result-grid" id="resultGrid">
                <div class="result-item" id="currentMealItem">
                    <div class="rlabel">本餐热量</div>
                    <div class="rvalue" id="currentMealKcal"><span class="placeholder">--</span><span class="runit">千卡</span></div>
                </div>
                <div class="result-item" id="todayTotalItem">
                    <div class="rlabel">今日总热量</div>
                    <div class="rvalue" id="todayTotalKcal"><span class="placeholder">0</span><span class="runit">千卡</span></div>
                </div>
                <div class="result-item target-item" id="targetItem">
                    <div class="rlabel">目标热量</div>
                    <div class="rvalue" id="targetKcal"><span class="placeholder">--</span><span class="runit">千卡</span></div>
                </div>
            </div>
            <!-- 差值提示放在结果区下方 -->
            <div class="diff-hint" id="diffHint"></div>
        </div>

        <!-- ===== 卡片3:日历 ===== -->
        <div class="card calendar-wrap">
            <div class="calendar-nav">
                <button class="cal-btn" id="calPrev">&#8249;</button>
                <span class="cal-month" id="calMonth">--</span>
                <button class="cal-btn" id="calNext">&#8250;</button>
            </div>
            <div class="calendar-grid" id="calendarGrid"></div>
        </div>

        <!-- ===== 卡片4:饮食记录(点击日期后显示) ===== -->
        <div class="card record-card" id="recordCard">
            <div class="dd-header">
                <div class="dd-title" id="ddTitle">&#128203; 详细记录</div>
                <div class="dd-weight" id="ddWeight"></div>
            </div>
            <div id="ddList"></div>
        </div>

    </div>

    <!-- ===== 设置弹窗 ===== -->
    <div class="modal-overlay" id="settingsModal">
        <div class="modal-box">
            <div class="modal-title">&#9881;&#65039; 个人基础信息</div>
            <div class="settings-grid">
                <div class="field">
                    <label>年龄</label>
                    <input type="number" id="userAge" placeholder="岁" min="10" max="120" />
                </div>
                <div class="field">
                    <label>性别</label>
                    <select id="userGenderSelect">
                        <option value="male">男</option>
                        <option value="female">女</option>
                    </select>
                </div>
                <div class="field">
                    <label>身高 (cm)</label>
                    <input type="number" id="userHeight" placeholder="cm" min="100" max="250" step="0.1" />
                </div>
                <div class="field">
                    <label>运动量</label>
                    <select id="userActivity">
                        <option value="1.2">久坐</option>
                        <option value="1.375">轻度</option>
                        <option value="1.55">中度</option>
                        <option value="1.725">重度</option>
                        <option value="1.9">极重度</option>
                    </select>
                </div>
                <div class="field">
                    <label>体重 (kg)</label>
                    <input type="number" id="userWeight" placeholder="kg" min="20" max="300" step="0.1" />
                </div>
                <div class="field" style="display:flex;align-items:flex-end;">
                    <div style="width:100%;">
                        <label>目标</label>
                        <div class="settings-target-full" id="targetGroup">
                            <button data-target="maintain">维持</button>
                            <button data-target="lose" class="active">减肥</button>
                        </div>
                    </div>
                </div>
                <div class="settings-result" id="calcInfo">
                    <strong>估算</strong> · BMR: <span id="bmrDisplay">--</span> 千卡 &nbsp;|&nbsp; TDEE: <span id="tdeeDisplay">--</span> 千卡<br />
                    <span id="targetSuggestion">建议每日摄入 <strong>--</strong> 千卡</span>
                </div>
            </div>
            <div class="modal-actions">
                <button class="btn-close" id="settingsClose">取消</button>
                <button class="btn-save" id="settingsSave">保存</button>
            </div>
        </div>
    </div>

    <!-- ===== 主题选择弹窗 ===== -->
    <div class="modal-overlay" id="themeModal">
        <div class="modal-box">
            <div class="modal-title">&#127912; 选择主题色</div>
            <div class="theme-grid" id="themeGrid"></div>
            <div class="modal-actions">
                <button class="btn-dark-toggle" id="darkModeToggle">&#127769; 启用暗黑模式</button>
                <button class="btn-close" id="themeClose">关闭</button>
            </div>
        </div>
    </div>

    <!-- ===== 体重弹窗 ===== -->
    <div class="modal-overlay weight-modal" id="weightModal">
        <div class="modal-box">
            <div class="modal-title">&#9878;&#65039; 体重记录</div>
            <div class="compact-row">
                <span class="field-label">体重</span>
                <input type="number" id="weightInput" placeholder="kg" min="20" max="300" step="0.1" />
                <span style="font-size:14px;color:#888;margin:0 2px;">kg</span>
                <span style="font-size:14px;color:#888;margin:0 2px;">·</span>
                <select id="weightTime">
                    <option value="自动">自动</option>
                    <option value="早晨">早晨</option>
                    <option value="中午">中午</option>
                    <option value="晚上">晚上</option>
                    <option value="其他">其他</option>
                </select>
            </div>
            <div class="action-row">
                <span class="weight-jin" id="weightJinDisplay"></span>
                <div class="modal-actions">
                    <button class="btn-close" id="weightClose">取消</button>
                    <button class="btn-save" id="weightSave">保存</button>
                </div>
            </div>
            <div class="chart-container" id="weightChartContainer">
                <div class="chart-header">
                    <span class="chart-title">&#128201; 体重趋势</span>
                    <div class="period-group" id="weightPeriodGroup">
                        <button data-period="7" class="active">7天</button>
                        <button data-period="15">半月</button>
                        <button data-period="30">1月</button>
                        <button data-period="90">3月</button>
                        <button data-period="180">半年</button>
                        <button data-period="365">1年</button>
                    </div>
                </div>
                <canvas id="weightChartCanvas"></canvas>
                <div class="no-data" id="weightNoData" style="display:none;">暂无体重数据</div>
            </div>
        </div>
    </div>

    <!-- ===== 备份弹窗 ===== -->
    <div class="modal-overlay" id="backupModal">
        <div class="modal-box">
            <div class="modal-title">&#128190; 数据备份</div>
            <div class="backup-section">
                <div class="backup-section-title">选择导出内容</div>
                <div class="backup-checkboxes">
                    <label class="backup-checkbox-item">
                        <input type="checkbox" id="backupPersonal" checked />
                        <span>个人基本信息</span>
                    </label>
                    <label class="backup-checkbox-item">
                        <input type="checkbox" id="backupTheme" checked />
                        <span>主题配置</span>
                    </label>
                    <label class="backup-checkbox-item">
                        <input type="checkbox" id="backupTags" checked />
                        <span>自定义标签</span>
                    </label>
                    <label class="backup-checkbox-item backup-full-row">
                        <input type="checkbox" id="backupRecords" checked />
                        <span>全部历史记录(饮食+热量)</span>
                    </label>
                    <label class="backup-checkbox-item backup-full-row">
                        <input type="checkbox" id="backupWeight" checked />
                        <span>体重记录</span>
                    </label>
                </div>
            </div>
            <div class="backup-tip">导入数据将覆盖当前对应分类的数据</div>
            <input type="file" id="backupFileInput" accept=".json,.txt" style="display:none;" />
            <div class="modal-actions">
                <button class="btn-close" id="backupImportBtn">导入数据</button>
                <button class="btn-save" id="backupExportBtn">导出数据</button>
            </div>
        </div>
    </div>

    <!-- ===== 遮罩 (标签) ===== -->
    <div class="overlay-tag" id="overlayTag"></div>

    <!-- ===== 删除确认框 ===== -->
    <div class="modal-confirm" id="confirmModal">
        <div class="mc-title">&#9888;&#65039; 确认删除</div>
        <div class="mc-desc" id="confirmDesc">确定要删除该记录吗?</div>
        <div class="mc-btns">
            <button class="mc-cancel" id="confirmCancel">取消</button>
            <button class="mc-confirm" id="confirmOk">删除</button>
        </div>
    </div>

    <!-- ===== Toast 提示 ===== -->
    <div class="toast-container" id="toastContainer"></div>

    <!-- ===== 悬浮标签 ===== -->
    <div class="tag-fab" id="tagFab">
        <button class="fab-btn" id="fabToggle"><span class="fab-icon">&#127991;&#65039;</span></button>
    </div>

    <!-- ===== 标签面板 ===== -->
    <div class="tag-panel" id="tagPanel">
        <div class="panel-header">
            <span class="ph-title">&#127991;&#65039; 常用标签</span>
            <button class="ph-close" id="panelClose">&#10005;</button>
        </div>
        <div class="panel-body" id="panelBody">
            <div class="tag-count" id="tagCount">0 / 50</div>
            <div id="tagList"></div>
        </div>
    </div>

    <script>
        (function() {
            'use strict';

            // ============================================================
            //  数据层
            // ============================================================
            const STORAGE_KEY = 'food_cal_data_v12';
            const SETTINGS_KEY = 'user_settings_v5';
            const THEME_KEY = 'user_theme_v2';
            const INPUT_CACHE_KEY = 'food_input_cache_v1';

            const DEFAULT_TAGS = [
                { name: '鸡胸肉', defaultWeight: 100, unit: 'g', energy: 550 },
                { name: '牛肉(瘦)', defaultWeight: 100, unit: 'g', energy: 480 },
                { name: '猪里脊', defaultWeight: 100, unit: 'g', energy: 520 },
                { name: '三文鱼', defaultWeight: 100, unit: 'g', energy: 600 },
                { name: '鳕鱼', defaultWeight: 100, unit: 'g', energy: 370 },
                { name: '鸡蛋(全蛋)', defaultWeight: 50, unit: 'g', energy: 315 },
                { name: '蛋白', defaultWeight: 50, unit: 'g', energy: 85 },
                { name: '脱脂牛奶', defaultWeight: 100, unit: 'ml', energy: 140 },
                { name: '纯牛奶', defaultWeight: 100, unit: 'ml', energy: 270 },
                { name: '希腊酸奶', defaultWeight: 100, unit: 'g', energy: 320 },
                { name: '燕麦片', defaultWeight: 100, unit: 'g', energy: 1500 },
                { name: '全麦面包', defaultWeight: 100, unit: 'g', energy: 1000 },
                { name: '糙米', defaultWeight: 100, unit: 'g', energy: 480 },
                { name: '藜麦', defaultWeight: 100, unit: 'g', energy: 500 },
                { name: '红薯', defaultWeight: 100, unit: 'g', energy: 360 },
                { name: '玉米', defaultWeight: 100, unit: 'g', energy: 350 },
                { name: '西兰花', defaultWeight: 100, unit: 'g', energy: 140 },
                { name: '菠菜', defaultWeight: 100, unit: 'g', energy: 100 },
                { name: '生菜', defaultWeight: 100, unit: 'g', energy: 60 },
                { name: '番茄', defaultWeight: 100, unit: 'g', energy: 80 },
                { name: '黄瓜', defaultWeight: 100, unit: 'g', energy: 65 },
                { name: '胡萝卜', defaultWeight: 100, unit: 'g', energy: 110 },
                { name: '苹果', defaultWeight: 100, unit: 'g', energy: 218 },
                { name: '香蕉', defaultWeight: 100, unit: 'g', energy: 381 },
                { name: '蓝莓', defaultWeight: 100, unit: 'g', energy: 240 },
                { name: '草莓', defaultWeight: 100, unit: 'g', energy: 130 },
                { name: '牛油果', defaultWeight: 100, unit: 'g', energy: 670 },
                { name: '豆腐', defaultWeight: 100, unit: 'g', energy: 320 },
                { name: '豆浆(无糖)', defaultWeight: 100, unit: 'ml', energy: 120 },
                { name: '奶酪(低脂)', defaultWeight: 100, unit: 'g', energy: 500 },
            ];

            function getDefaultData() {
                const now = Date.now();
                return {
                    tags: DEFAULT_TAGS.map((t, i) => ({ ...t, id: 'tag_' + now + '_' + i, lastUsed: now - i })),
                    records: {},
                    weightRecords: {}
                };
            }

            let data = loadData();
            let userSettings = loadSettings();
            let currentTheme = loadTheme();

            function loadData() {
                try {
                    const raw = localStorage.getItem(STORAGE_KEY);
                    if (raw) {
                        const parsed = JSON.parse(raw);
                        if (parsed && typeof parsed === 'object' && parsed.tags && parsed.records) {
                            if (!parsed.weightRecords) parsed.weightRecords = {};
                            const now = Date.now();
                            parsed.tags.forEach((t, i) => {
                                if (!t.unit) t.unit = 'g';
                                if (!t.lastUsed) t.lastUsed = now - i;
                            });
                            return parsed;
                        }
                    }
                } catch (_) {}
                return getDefaultData();
            }

            function saveData() {
                try {
                    localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
                } catch (_) {}
            }

            function loadSettings() {
                try {
                    const raw = localStorage.getItem(SETTINGS_KEY);
                    if (raw) {
                        const s = JSON.parse(raw);
                        if (s && typeof s === 'object') return s;
                    }
                } catch (_) {}
                return {
                    age: '',
                    gender: 'male',
                    height: '',
                    weight: '',
                    activity: 1.2,
                    target: 'lose'
                };
            }

            function saveSettings(settings) {
                userSettings = settings;
                try {
                    localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
                } catch (_) {}
            }

            function loadTheme() {
                try {
                    const t = localStorage.getItem(THEME_KEY);
                    if (t) return t;
                } catch (_) {}
                return 'theme-1';
            }

            function saveTheme(theme) {
                currentTheme = theme;
                try {
                    localStorage.setItem(THEME_KEY, theme);
                } catch (_) {}
            }

            // ============================================================
            //  食品输入缓存
            // ============================================================
            function loadInputCache() {
                try {
                    const raw = localStorage.getItem(INPUT_CACHE_KEY);
                    if (raw) {
                        const c = JSON.parse(raw);
                        if (c && typeof c === 'object') return c;
                    }
                } catch (_) {}
                return null;
            }

            function saveInputCache() {
                const cache = {
                    foodName: document.getElementById('foodName').value,
                    defaultWeight: document.getElementById('defaultWeight').value,
                    defaultUnit: document.getElementById('defaultUnit').value,
                    energy: document.getElementById('energy').value,
                    serving: document.getElementById('serving').value,
                    servingUnit: document.getElementById('servingUnit').value
                };
                try {
                    localStorage.setItem(INPUT_CACHE_KEY, JSON.stringify(cache));
                } catch (_) {}
            }

            function applyInputCache() {
                const cache = loadInputCache();
                if (cache) {
                    if (cache.foodName !== undefined) document.getElementById('foodName').value = cache.foodName;
                    if (cache.defaultWeight !== undefined) document.getElementById('defaultWeight').value = cache.defaultWeight;
                    if (cache.defaultUnit !== undefined) document.getElementById('defaultUnit').value = cache.defaultUnit;
                    if (cache.energy !== undefined) document.getElementById('energy').value = cache.energy;
                    if (cache.serving !== undefined) document.getElementById('serving').value = cache.serving;
                    if (cache.servingUnit !== undefined) document.getElementById('servingUnit').value = cache.servingUnit;
                }
            }

            // ============================================================
            //  数据导出/导入
            // ============================================================
            function exportBackupData(options) {
                const exportData = {
                    version: 1,
                    exportTime: new Date().toISOString(),
                    app: '热量计算+体重记录'
                };
                if (options.personal) {
                    exportData.personal = userSettings;
                }
                if (options.theme) {
                    exportData.theme = currentTheme;
                }
                if (options.tags) {
                    exportData.tags = data.tags;
                }
                if (options.records) {
                    exportData.records = data.records;
                }
                if (options.weight) {
                    exportData.weightRecords = data.weightRecords;
                }
                return exportData;
            }

            function formatBackupFilename() {
                const d = new Date();
                const y = d.getFullYear();
                const m = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                const h = String(d.getHours()).padStart(2, '0');
                const min = String(d.getMinutes()).padStart(2, '0');
                const s = String(d.getSeconds()).padStart(2, '0');
                return '热量计算加体重记录_' + y + m + day + '_' + h + min + s + '.json';
            }

            function downloadFile(content, filename) {
                const blob = new Blob([content], { type: 'application/json;charset=utf-8' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = filename;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                setTimeout(() => { URL.revokeObjectURL(url); }, 100);
            }

            function doExport() {
                const options = {
                    personal: document.getElementById('backupPersonal').checked,
                    theme: document.getElementById('backupTheme').checked,
                    tags: document.getElementById('backupTags').checked,
                    records: document.getElementById('backupRecords').checked,
                    weight: document.getElementById('backupWeight').checked
                };
                const hasAny = options.personal || options.theme || options.tags || options.records || options.weight;
                if (!hasAny) {
                    alert('请至少选择一项导出内容');
                    return;
                }
                const exportData = exportBackupData(options);
                const content = JSON.stringify(exportData, null, 2);
                const filename = formatBackupFilename();
                downloadFile(content, filename);
            }

            function importBackupData(parsedData) {
                let importedCount = 0;
                if (parsedData.personal && typeof parsedData.personal === 'object') {
                    saveSettings(parsedData.personal);
                    importedCount++;
                }
                if (parsedData.theme && typeof parsedData.theme === 'string') {
                    applyTheme(parsedData.theme);
                    importedCount++;
                }
                if (parsedData.tags && Array.isArray(parsedData.tags)) {
                    data.tags = parsedData.tags;
                    saveData();
                    importedCount++;
                }
                if (parsedData.records && typeof parsedData.records === 'object') {
                    data.records = parsedData.records;
                    saveData();
                    importedCount++;
                }
                if (parsedData.weightRecords && typeof parsedData.weightRecords === 'object') {
                    data.weightRecords = parsedData.weightRecords;
                    saveData();
                    importedCount++;
                }
                return importedCount;
            }

            function handleImportFile(file) {
                const reader = new FileReader();
                reader.onload = function(e) {
                    try {
                        const content = e.target.result;
                        const parsed = JSON.parse(content);
                        if (!parsed || typeof parsed !== 'object') {
                            alert('文件格式不正确');
                            return;
                        }
                        const count = importBackupData(parsed);
                        if (count === 0) {
                            alert('未找到可导入的数据');
                            return;
                        }
                        alert('&#9989; 导入成功,共导入 ' + count + ' 类数据');
                        closeBackupModal();
                        renderTags();
                        renderCalendar();
                        computePreview();
                        if (selectedDateStr) {
                            renderRecordCard(selectedDateStr);
                        }
                    } catch (err) {
                        alert('导入失败:文件格式错误');
                    }
                };
                reader.onerror = function() {
                    alert('读取文件失败');
                };
                reader.readAsText(file, 'UTF-8');
            }

            // ============================================================
            //  主题
            // ============================================================
            const THEMES = [
                { id: 'theme-1', name: '橙红', colors: ['#ff7e5f', '#feb47b'] },
                { id: 'theme-2', name: '翠绿', colors: ['#11998e', '#38ef7d'] },
                { id: 'theme-3', name: '天蓝', colors: ['#2193b0', '#6dd5ed'] },
                { id: 'theme-4', name: '紫罗兰', colors: ['#8E2DE2', '#4A00E0'] },
                { id: 'theme-5', name: '粉红', colors: ['#f093fb', '#f5576c'] },
                { id: 'theme-6', name: '青草', colors: ['#a8e063', '#56ab2f'] },
                { id: 'theme-7', name: '日落', colors: ['#f2994a', '#f2c94c'] },
                { id: 'theme-8', name: '冰川', colors: ['#00c6fb', '#005bea'] },
                { id: 'theme-9', name: '玫瑰', colors: ['#cb2d3e', '#ef473a'] },
                { id: 'theme-10', name: '薄荷', colors: ['#00b09b', '#96c93d'] },
                { id: 'theme-11', name: '星空', colors: ['#1a2a6c', '#b21f1f'] },
                { id: 'theme-12', name: '碳灰', colors: ['#2c3e50', '#3498db'] },
            ];

            function applyTheme(themeId) {
                const root = document.documentElement;
                root.className = themeId;
                const theme = THEMES.find(t => t.id === themeId);
                if (theme) {
                    const [start, end] = theme.colors;
                    root.style.setProperty('--primary-start', start);
                    root.style.setProperty('--primary-end', end);
                    root.style.setProperty('--active-bg', start);
                    root.style.setProperty('--primary-shadow', start + '40');
                }
                saveTheme(themeId);
            }

            // ============================================================
            //  工具
            // ============================================================
            function formatDate(d) {
                const m = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                const wd = ['日', '一', '二', '三', '四', '五', '六'][d.getDay()];
                return m + '月' + day + '日 周' + wd;
            }

            function formatDateShort(d) {
                const m = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                return m + '/' + day;
            }

            function getTodayStr() {
                const d = new Date();
                const y = d.getFullYear();
                const m = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                return y + '-' + m + '-' + day;
            }

            function parseDate(str) {
                const parts = str.split('-').map(Number);
                return new Date(parts[0], parts[1] - 1, parts[2]);
            }

            function getAutoMeal() {
                const h = new Date().getHours();
                if (h >= 5 && h < 9) return '早餐';
                if (h >= 11 && h < 14) return '中餐';
                if (h >= 17 && h < 21) return '晚餐';
                return '加餐';
            }

            function getNumberValue(el) {
                const v = el.value.trim();
                if (v === '') return NaN;
                const n = parseFloat(v);
                return isNaN(n) ? NaN : n;
            }

            function generateId() {
                return 'id_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6);
            }

            function escapeHtml(str) {
                const div = document.createElement('div');
                div.textContent = str;
                return div.innerHTML;
            }

            // ============================================================
            //  数据操作
            // ============================================================
            function getRecordsForDate(dateStr) {
                return data.records[dateStr] || [];
            }

            function getTotalKcalForDate(dateStr) {
                const recs = getRecordsForDate(dateStr);
                let total = 0;
                recs.forEach(r => { total += r.totalKcal || 0; });
                return total;
            }

            function getWeightForDate(dateStr) {
                return data.weightRecords[dateStr] !== undefined ? data.weightRecords[dateStr] : null;
            }

            function setWeightForDate(dateStr, weightKg) {
                data.weightRecords[dateStr] = weightKg;
                saveData();
            }

            function getWeightRecordsInRange(startDate, endDate) {
                const records = data.weightRecords || {};
                const result = [];
                const start = new Date(startDate);
                const end = new Date(endDate);
                for (const [dateStr, weight] of Object.entries(records)) {
                    const d = parseDate(dateStr);
                    if (d >= start && d <= end) {
                        result.push({ date: dateStr, weight: weight });
                    }
                }
                result.sort((a, b) => a.date.localeCompare(b.date));
                return result;
            }

            function getRecentRange(days) {
                const today = new Date();
                const end = new Date(today);
                const start = new Date(today);
                start.setDate(start.getDate() - days + 1);
                return { start: start, end: end };
            }

            // ============================================================
            //  设置相关
            // ============================================================
            function getTargetCalories() {
                const age = parseInt(userSettings.age);
                const gender = userSettings.gender;
                const height = parseFloat(userSettings.height);
                const weight = parseFloat(userSettings.weight);
                const activity = parseFloat(userSettings.activity);
                const target = userSettings.target || 'lose';

                if (isNaN(age) || isNaN(height) || isNaN(weight) || age <= 0 || height <= 0 || weight <= 0) {
                    return { tdee: null, targetKcal: null };
                }

                let bmr;
                if (gender === 'male') {
                    bmr = 10 * weight + 6.25 * height - 5 * age + 5;
                } else {
                    bmr = 10 * weight + 6.25 * height - 5 * age - 161;
                }
                const tdee = bmr * activity;

                let targetKcal;
                if (target === 'maintain') {
                    targetKcal = tdee;
                } else {
                    targetKcal = tdee - 400;
                    if (targetKcal < 1000) targetKcal = 1000;
                }
                return { tdee, targetKcal };
            }

            function getTargetDisplay() {
                const result = getTargetCalories();
                if (result.targetKcal === null) return null;
                return Math.round(result.targetKcal);
            }

            // ============================================================
            //  渲染:日期+餐次
            // ============================================================
            function updateDateDisplay() {
                const d = new Date();
                document.getElementById('dateDisplay').textContent = formatDate(d);
            }

            // ============================================================
            //  渲染:结果区 + 差值提示
            // ============================================================
            function updateResults(mealKcal, todayTotal, target) {
                const mealEl = document.getElementById('currentMealKcal');
                const todayEl = document.getElementById('todayTotalKcal');
                const targetEl = document.getElementById('targetKcal');
                const diffEl = document.getElementById('diffHint');

                if (mealKcal !== null && !isNaN(mealKcal) && mealKcal > 0) {
                    mealEl.innerHTML = mealKcal.toFixed(1) + '<span class="runit"> 千卡</span>';
                } else {
                    mealEl.innerHTML = '<span class="placeholder">--</span><span class="runit"> 千卡</span>';
                }

                const todayVal = todayTotal || 0;
                todayEl.innerHTML = todayVal.toFixed(1) + '<span class="runit"> 千卡</span>';

                const todayItem = document.getElementById('todayTotalItem');
                todayItem.className = 'result-item';
                if (target !== null && target > 0 && todayVal > 0) {
                    const ratio = todayVal / target;
                    if (ratio > 1.0) {
                        todayItem.classList.add('highlight-red');
                    } else if (ratio >= 0.9) {
                        todayItem.classList.add('highlight-yellow');
                    } else {
                        todayItem.classList.add('highlight-green');
                    }
                } else if (todayVal > 0) {
                    todayItem.classList.add('highlight-green');
                }

                if (target !== null) {
                    targetEl.innerHTML = target + '<span class="runit"> 千卡</span>';
                } else {
                    targetEl.innerHTML = '<span class="placeholder">--</span><span class="runit"> 千卡</span>';
                }

                // 差值提示
                if (target === null || target <= 0) {
                    diffEl.textContent = '请先在「设置」中填写个人信息以计算目标热量';
                    diffEl.className = 'diff-hint';
                    return;
                }
                const diff = todayVal - target;
                const ratio = todayVal / target;
                if (diff > 0) {
                    diffEl.innerHTML = '今日热量超标 <span class="diff-value">' + diff.toFixed(1) + '</span> 千卡';
                    diffEl.className = 'diff-hint danger';
                } else if (ratio >= 0.9) {
                    diffEl.innerHTML = '今日还可以吃 <span class="diff-value">' + Math.abs(diff).toFixed(1) + '</span> 千卡食物';
                    diffEl.className = 'diff-hint warning';
                } else {
                    diffEl.innerHTML = '今日还可以吃 <span class="diff-value">' + Math.abs(diff).toFixed(1) + '</span> 千卡食物';
                    diffEl.className = 'diff-hint success';
                }
            }

            // ============================================================
            //  核心计算
            // ============================================================
            function computePreview() {
                const defaultW = getNumberValue(document.getElementById('defaultWeight'));
                const defaultUnit = document.getElementById('defaultUnit').value;
                const energy = getNumberValue(document.getElementById('energy'));
                const servingRaw = getNumberValue(document.getElementById('serving'));
                const servingUnit = document.getElementById('servingUnit').value;

                const hasDefault = !isNaN(defaultW) && defaultW > 0;
                const hasEnergy = !isNaN(energy) && energy >= 0;
                const hasServing = !isNaN(servingRaw) && servingRaw >= 0;

                let mealKcal = null;
                if (hasDefault && hasEnergy && hasServing) {
                    let servingUnits = servingRaw;
                    if (servingUnit === 'kg') servingUnits = servingRaw * 1000;
                    const energyPerUnit = energy / defaultW;
                    const totalKj = energyPerUnit * servingUnits;
                    mealKcal = totalKj / 4.184;
                }

                const todayStr = getTodayStr();
                const todayTotal = getTotalKcalForDate(todayStr);
                const target = getTargetDisplay();

                updateResults(mealKcal, todayTotal, target);
            }

            // ============================================================
            //  保存记录
            // ============================================================
            function saveRecord() {
                const name = document.getElementById('foodName').value.trim();
                const defaultW = getNumberValue(document.getElementById('defaultWeight'));
                const defaultUnit = document.getElementById('defaultUnit').value;
                const energy = getNumberValue(document.getElementById('energy'));
                const servingRaw = getNumberValue(document.getElementById('serving'));
                const servingUnit = document.getElementById('servingUnit').value;

                if (!name) { showToast('请填写食物名称', 'error'); return; }
                if (isNaN(defaultW) || defaultW <= 0) { showToast('请填写有效的每份量', 'error'); return; }
                if (isNaN(energy) || energy < 0) { showToast('请填写有效的能量 (kJ)', 'error'); return; }
                if (isNaN(servingRaw) || servingRaw <= 0) { showToast('请填写有效的食用量', 'error'); return; }

                let servingUnits = servingRaw;
                if (servingUnit === 'kg') servingUnits = servingRaw * 1000;

                const energyPerUnit = energy / defaultW;
                const totalKj = energyPerUnit * servingUnits;
                const totalKcal = totalKj / 4.184;

                const todayStr = getTodayStr();
                const meal = document.getElementById('mealSelect').value || '加餐';

                const record = {
                    id: generateId(),
                    tagName: name,
                    defaultWeight: defaultW,
                    unit: defaultUnit,
                    energy: energy,
                    serving: servingUnits,
                    servingUnit: servingUnit,
                    totalKcal: totalKcal,
                    meal: meal,
                    timestamp: new Date().toISOString()
                };

                if (!data.records[todayStr]) data.records[todayStr] = [];
                data.records[todayStr].push(record);

                const matchedTag = data.tags.find(t =>
                    t.name === name && t.defaultWeight === defaultW && t.unit === defaultUnit && t.energy === energy
                );
                if (matchedTag) {
                    matchedTag.lastUsed = Date.now();
                }

                saveData();

                showToast('&#9989; 记录已保存', 'success');
                computePreview();
                renderCalendar();
                // 如果当前选中的是今天,刷新记录卡片
                if (selectedDateStr === todayStr) {
                    renderRecordCard(todayStr);
                }
            }

            // ============================================================
            //  Toast 提示
            // ============================================================
            let toastTimer = null;

            function showToast(message, type) {
                const container = document.getElementById('toastContainer');
                const toast = document.createElement('div');
                toast.className = 'toast-item' + (type ? ' ' + type : '');
                toast.textContent = message;
                container.appendChild(toast);

                requestAnimationFrame(() => {
                    requestAnimationFrame(() => {
                        toast.classList.add('show');
                    });
                });

                setTimeout(() => {
                    toast.classList.remove('show');
                    setTimeout(() => {
                        if (toast.parentNode) {
                            toast.parentNode.removeChild(toast);
                        }
                    }, 300);
                }, 1500);
            }

            // ============================================================
            //  通用确认弹窗
            // ============================================================
            let confirmCallback = null;

            function showConfirm(title, desc, confirmText, onConfirm) {
                document.querySelector('#confirmModal .mc-title').textContent = title;
                document.getElementById('confirmDesc').textContent = desc;
                document.querySelector('#confirmModal .mc-confirm').textContent = confirmText;
                confirmCallback = onConfirm;
                document.getElementById('confirmModal').classList.add('show');
            }

            function closeConfirm() {
                confirmCallback = null;
                document.getElementById('confirmModal').classList.remove('show');
            }

            document.getElementById('confirmOk').addEventListener('click', function() {
                const cb = confirmCallback;
                closeConfirm();
                if (cb) cb();
            });

            document.getElementById('confirmCancel').addEventListener('click', closeConfirm);

            // ============================================================
            //  删除记录
            // ============================================================
            function confirmDeleteRecord(recordId) {
                const todayStr = getTodayStr();
                showConfirm('&#9888;&#65039; 确认删除', '确定要删除该记录吗?', '删除', function() {
                    const records = data.records[todayStr] || [];
                    const idx = records.findIndex(r => r.id === recordId);
                    if (idx !== -1) {
                        records.splice(idx, 1);
                        if (records.length === 0) {
                            delete data.records[todayStr];
                        }
                        saveData();
                        renderCalendar();
                        renderRecordCard(todayStr);
                        computePreview();
                    }
                });
            }

            // ============================================================
            //  渲染:日历 + 记录卡片
            // ============================================================
            let calYear = new Date().getFullYear();
            let calMonth = new Date().getMonth();
            let selectedDateStr = getTodayStr();

            function renderCalendar() {
                const grid = document.getElementById('calendarGrid');
                const monthLabel = document.getElementById('calMonth');
                const year = calYear,
                    month = calMonth;
                monthLabel.textContent = year + '年' + (month + 1) + '月';

                const firstDayOfMonth = new Date(year, month, 1).getDay();
                let startOffset = firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1;

                const daysInMonth = new Date(year, month + 1, 0).getDate();
                const daysInPrev = new Date(year, month, 0).getDate();

                const target = getTargetDisplay();
                const todayStr = getTodayStr();

                let html = '';
                const weekdays = ['一', '二', '三', '四', '五', '六', '日'];
                weekdays.forEach(w => {
                    html += `<div class="cal-weekday">${w}</div>`;
                });

                // 上月
                for (let i = startOffset - 1; i >= 0; i--) {
                    const d = daysInPrev - i;
                    const dateObj = new Date(year, month - 1, d);
                    const fullDate = formatDateFull(dateObj);
                    const shortDate = formatDateShort(dateObj);
                    const kcal = getTotalKcalForDate(fullDate);
                    const weight = getWeightForDate(fullDate);
                    html += `<div class="cal-day other-month" data-date="${fullDate}">
                                <span class="day-number">${d}</span>
                                ${kcal > 0 ? `<span class="day-kcal">${kcal.toFixed(0)}</span>` : ''}
                                ${weight !== null ? `<span class="day-weight">${weight.toFixed(1)}</span>` : ''}
                             </div>`;
                }

                // 本月
                for (let d = 1; d <= daysInMonth; d++) {
                    const dateObj = new Date(year, month, d);
                    const fullDate = formatDateFull(dateObj);
                    const shortDate = formatDateShort(dateObj);
                    const kcal = getTotalKcalForDate(fullDate);
                    const weight = getWeightForDate(fullDate);
                    const isToday = fullDate === todayStr;
                    const isSelected = fullDate === selectedDateStr;
                    let statusClass = '';
                    if (kcal > 0 && target !== null && target > 0) {
                        const ratio = kcal / target;
                        if (ratio > 1.0) statusClass = 'status-red';
                        else if (ratio >= 0.9) statusClass = 'status-yellow';
                        else statusClass = 'status-green';
                    } else if (kcal > 0) {
                        statusClass = 'status-green';
                    }
                    let cls = 'cal-day';
                    if (isToday) cls += ' today';
                    if (isSelected) cls += ' selected';
                    if (statusClass) cls += ' ' + statusClass;
                    html += `<div class="${cls}" data-date="${fullDate}">
                                <span class="day-number">${d}</span>
                                ${kcal > 0 ? `<span class="day-kcal">${kcal.toFixed(0)}</span>` : ''}
                                ${weight !== null ? `<span class="day-weight">${weight.toFixed(1)}</span>` : ''}
                             </div>`;
                }

                // 下月填充
                const totalCells = startOffset + daysInMonth;
                const remaining = (7 - (totalCells % 7)) % 7;
                for (let d = 1; d <= remaining; d++) {
                    const dateObj = new Date(year, month + 1, d);
                    const fullDate = formatDateFull(dateObj);
                    const shortDate = formatDateShort(dateObj);
                    const kcal = getTotalKcalForDate(fullDate);
                    const weight = getWeightForDate(fullDate);
                    html += `<div class="cal-day other-month" data-date="${fullDate}">
                                <span class="day-number">${d}</span>
                                ${kcal > 0 ? `<span class="day-kcal">${kcal.toFixed(0)}</span>` : ''}
                                ${weight !== null ? `<span class="day-weight">${weight.toFixed(1)}</span>` : ''}
                             </div>`;
                }

                grid.innerHTML = html;

                // 点击日期
                grid.querySelectorAll('.cal-day').forEach(el => {
                    el.addEventListener('click', function() {
                        const fullDate = this.dataset.date;
                        if (fullDate) {
                            selectedDateStr = fullDate;
                            renderCalendar();
                            renderRecordCard(fullDate);
                        }
                    });
                });

                // 如果当前选中日期有记录,刷新记录卡片
                if (selectedDateStr) {
                    renderRecordCard(selectedDateStr);
                }
            }

            function formatDateFull(d) {
                const y = d.getFullYear();
                const m = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                return y + '-' + m + '-' + day;
            }

            function renderRecordCard(dateStr) {
                const card = document.getElementById('recordCard');
                const list = document.getElementById('ddList');
                const title = document.getElementById('ddTitle');
                const weightEl = document.getElementById('ddWeight');
                const records = getRecordsForDate(dateStr);
                const total = getTotalKcalForDate(dateStr);
                const weight = getWeightForDate(dateStr);
                const todayStr = getTodayStr();
                const isToday = (dateStr === todayStr);

                if (records.length === 0 && weight === null) {
                    card.classList.remove('show');
                    return;
                }

                card.classList.add('show');
                title.innerHTML = '&#128203; ' + dateStr + '  ·  总计 <span class="dd-total-kcal">' + total.toFixed(1) + ' 千卡</span>';
                if (weight !== null) {
                    weightEl.textContent = '&#9878;&#65039; ' + weight.toFixed(1) + ' kg';
                    weightEl.style.display = 'block';
                } else {
                    weightEl.style.display = 'none';
                }

                if (records.length === 0) {
                    list.innerHTML = '<div class="dd-empty">暂无饮食记录</div>';
                    return;
                }

                let html = '';
                const mealOrder = ['早餐', '中餐', '晚餐', '加餐'];
                const grouped = {};
                records.forEach(r => {
                    const m = r.meal || '加餐';
                    if (!grouped[m]) grouped[m] = [];
                    grouped[m].push(r);
                });
                mealOrder.forEach(m => {
                    if (grouped[m] && grouped[m].length > 0) {
                        html += `<div class="meal-group-label">${m}</div>`;
                        grouped[m].forEach(r => {
                            const name = r.tagName || '未命名';
                            const kcal = r.totalKcal || 0;
                            const gram = r.serving || 0;
                            const unit = r.unit || 'g';
                            let delBtn = '';
                            if (isToday) {
                                delBtn = `<button class="dd-del" data-id="${r.id}">&#10005;</button>`;
                            }
                            html += `<div class="dd-item">
                                        <span class="dd-name">${escapeHtml(name)} (${gram}${unit})</span>
                                        <span style="display:flex;align-items:center;">
                                            <span class="dd-kcal">${kcal.toFixed(1)} 千卡</span>
                                            ${delBtn}
                                        </span>
                                     </div>`;
                        });
                    }
                });
                list.innerHTML = html;

                if (isToday) {
                    list.querySelectorAll('.dd-del').forEach(btn => {
                        btn.addEventListener('click', function(e) {
                            e.stopPropagation();
                            const id = this.dataset.id;
                            confirmDeleteRecord(id);
                        });
                    });
                }
            }

            // ============================================================
            //  标签系统(简化版)
            // ============================================================
            const tagListEl = document.getElementById('tagList');
            const tagCountEl = document.getElementById('tagCount');

            function renderTags() {
                const tags = [...data.tags].sort((a, b) => (b.lastUsed || 0) - (a.lastUsed || 0));
                tagCountEl.textContent = tags.length + ' / 50';

                if (tags.length === 0) {
                    tagListEl.innerHTML =
                        '<div style="color:#b0b8c4;text-align:center;padding:16px 0;font-size:13px;">暂无标签</div>';
                    return;
                }

                let html = '';
                tags.forEach((tag) => {
                    const unitDisplay = tag.unit || 'g';
                    html += `<div class="tag-item" data-id="${tag.id}">
                                <span class="tag-name">${escapeHtml(tag.name)}</span>
                                <span style="font-size:10px;color:#999;flex-shrink:0;">${tag.defaultWeight}${unitDisplay}/${tag.energy}kJ</span>
                                <button class="tag-del" data-id="${tag.id}">&#10005;</button>
                             </div>`;
                });
                tagListEl.innerHTML = html;

                tagListEl.querySelectorAll('.tag-del').forEach(btn => {
                    btn.addEventListener('click', function(e) {
                        e.stopPropagation();
                        const id = this.dataset.id;
                        const tag = data.tags.find(t => t.id === id);
                        if (tag) {
                            const tagId = id;
                            const tagName = tag.name;
                            showConfirm('&#9888;&#65039; 确认删除', '确定要删除标签 "' + tagName + '" 吗?', '删除', function() {
                                data.tags = data.tags.filter(t => t.id !== tagId);
                                saveData();
                                renderTags();
                                showToast('&#9989; 标签已删除', 'success');
                            });
                        }
                    });
                });

                tagListEl.querySelectorAll('.tag-item').forEach(el => {
                    el.addEventListener('click', function(e) {
                        if (e.target.closest('.tag-del')) return;
                        const id = this.dataset.id;
                        const tag = data.tags.find(t => t.id === id);
                        if (tag) {
                            tagListEl.querySelectorAll('.tag-item').forEach(item => {
                                item.classList.remove('tag-selected');
                            });
                            this.classList.add('tag-selected');
                            setTimeout(() => {
                                document.getElementById('foodName').value = tag.name;
                                document.getElementById('defaultWeight').value = tag.defaultWeight;
                                document.getElementById('defaultUnit').value = tag.unit || 'g';
                                document.getElementById('energy').value = tag.energy;
                                const unit = tag.unit || 'g';
                                document.getElementById('servingUnit').value = (unit === 'ml') ? 'ml' : 'g';
                                computePreview();
                                closePanel();
                            }, 150);
                        }
                    });
                });
            }

            // ============================================================
            //  标签面板控制
            // ============================================================
            const fabBtn = document.getElementById('fabToggle');
            const panel = document.getElementById('tagPanel');
            const overlayTag = document.getElementById('overlayTag');
            const panelClose = document.getElementById('panelClose');

            function openPanel() {
                panel.classList.add('open');
                overlayTag.classList.add('show');
                renderTags();
            }

            function closePanel() {
                panel.classList.remove('open');
                overlayTag.classList.remove('show');
            }

            fabBtn.addEventListener('click', function() {
                if (panel.classList.contains('open')) closePanel();
                else openPanel();
            });
            panelClose.addEventListener('click', closePanel);
            overlayTag.addEventListener('click', closePanel);

            // ============================================================
            //  悬浮按钮拖动
            // ============================================================
            const tagFab = document.getElementById('tagFab');
            const FAB_POS_KEY = 'tagFabPos';

            function loadFabPos() {
                try {
                    const saved = localStorage.getItem(FAB_POS_KEY);
                    if (saved) {
                        const pos = JSON.parse(saved);
                        if (pos.right !== undefined) tagFab.style.right = pos.right + 'px';
                        if (pos.bottom !== undefined) tagFab.style.bottom = pos.bottom + 'px';
                        if (pos.left !== undefined) {
                            tagFab.style.left = pos.left + 'px';
                            tagFab.style.right = 'auto';
                        }
                    }
                } catch (_) {}
            }

            function saveFabPos(right, bottom, left) {
                try {
                    const pos = {};
                    if (left !== undefined) pos.left = left;
                    else pos.right = right;
                    pos.bottom = bottom;
                    localStorage.setItem(FAB_POS_KEY, JSON.stringify(pos));
                } catch (_) {}
            }

            let isDragging = false;
            let dragMoved = false;
            let startX = 0;
            let startY = 0;
            let startRight = 0;
            let startBottom = 0;
            let startLeft = 0;
            let isLeftSide = false;

            function onDragStart(e) {
                isDragging = true;
                dragMoved = false;
                const rect = tagFab.getBoundingClientRect();
                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;
                startX = clientX;
                startY = clientY;
                startRight = window.innerWidth - rect.right;
                startBottom = window.innerHeight - rect.bottom;
                startLeft = rect.left;
                isLeftSide = (rect.left < window.innerWidth / 2);
                tagFab.style.transition = 'none';
            }

            function onDragMove(e) {
                if (!isDragging) return;
                e.preventDefault();
                const clientX = e.touches ? e.touches[0].clientX : e.clientX;
                const clientY = e.touches ? e.touches[0].clientY : e.clientY;
                const dx = clientX - startX;
                const dy = clientY - startY;
                if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
                    dragMoved = true;
                }
                let newLeft, newRight, newBottom;
                if (isLeftSide) {
                    newLeft = startLeft + dx;
                    newLeft = Math.max(4, Math.min(window.innerWidth - 56, newLeft));
                    tagFab.style.left = newLeft + 'px';
                    tagFab.style.right = 'auto';
                } else {
                    newRight = startRight - dx;
                    newRight = Math.max(4, Math.min(window.innerWidth - 56, newRight));
                    tagFab.style.right = newRight + 'px';
                    tagFab.style.left = 'auto';
                }
                newBottom = startBottom - dy;
                newBottom = Math.max(4, Math.min(window.innerHeight - 56, newBottom));
                tagFab.style.bottom = newBottom + 'px';
            }

            function onDragEnd(e) {
                if (!isDragging) return;
                isDragging = false;
                tagFab.style.transition = '';
                if (dragMoved) {
                    const rect = tagFab.getBoundingClientRect();
                    const bottom = window.innerHeight - rect.bottom;
                    const isLeft = rect.left < window.innerWidth / 2;
                    if (isLeft) {
                        saveFabPos(undefined, bottom, rect.left);
                    } else {
                        const right = window.innerWidth - rect.right;
                        saveFabPos(right, bottom, undefined);
                    }
                }
            }

            fabBtn.addEventListener('mousedown', function(e) {
                onDragStart(e);
            });
            document.addEventListener('mousemove', function(e) {
                onDragMove(e);
            });
            document.addEventListener('mouseup', function(e) {
                onDragEnd(e);
            });

            fabBtn.addEventListener('touchstart', function(e) {
                onDragStart(e);
            }, { passive: false });
            document.addEventListener('touchmove', function(e) {
                onDragMove(e);
            }, { passive: false });
            document.addEventListener('touchend', function(e) {
                onDragEnd(e);
            });

            fabBtn.addEventListener('click', function(e) {
                if (dragMoved) {
                    e.stopImmediatePropagation();
                    e.preventDefault();
                }
            }, true);

            loadFabPos();

            // ============================================================
            //  设置弹窗
            // ============================================================
            const settingsModal = document.getElementById('settingsModal');
            const settingsToggle = document.getElementById('settingsToggle');
            const settingsClose = document.getElementById('settingsClose');
            const settingsSave = document.getElementById('settingsSave');

            function openSettings() {
                document.getElementById('userAge').value = userSettings.age || '';
                document.getElementById('userGenderSelect').value = userSettings.gender || 'male';
                document.getElementById('userHeight').value = userSettings.height || '';
                document.getElementById('userWeight').value = userSettings.weight || '';
                document.getElementById('userActivity').value = userSettings.activity || '1.2';
                const target = userSettings.target || 'lose';
                document.querySelectorAll('#targetGroup button').forEach(btn => {
                    btn.classList.toggle('active', btn.dataset.target === target);
                });
                updateCalcInfo();
                settingsModal.classList.add('show');
            }

            function closeSettings() {
                settingsModal.classList.remove('show');
            }

            settingsToggle.addEventListener('click', openSettings);
            settingsClose.addEventListener('click', closeSettings);
            settingsModal.addEventListener('click', function(e) {
                if (e.target === this) closeSettings();
            });

            document.querySelectorAll('#targetGroup button').forEach(btn => {
                btn.addEventListener('click', function() {
                    document.querySelectorAll('#targetGroup button').forEach(b => b.classList.remove('active'));
                    this.classList.add('active');
                    userSettings.target = this.dataset.target;
                    updateCalcInfo();
                });
            });

            ['userAge', 'userHeight', 'userWeight', 'userActivity', 'userGenderSelect'].forEach(id => {
                const el = document.getElementById(id);
                el.addEventListener('input', updateCalcInfo);
                el.addEventListener('change', updateCalcInfo);
            });

            function updateCalcInfo() {
                const age = parseInt(document.getElementById('userAge').value);
                const gender = document.getElementById('userGenderSelect').value;
                const height = parseFloat(document.getElementById('userHeight').value);
                const weight = parseFloat(document.getElementById('userWeight').value);
                const activity = parseFloat(document.getElementById('userActivity').value);
                const target = document.querySelector('#targetGroup .active')?.dataset.target || 'lose';

                const bmrEl = document.getElementById('bmrDisplay');
                const tdeeEl = document.getElementById('tdeeDisplay');
                const suggEl = document.getElementById('targetSuggestion');

                if (isNaN(age) || isNaN(height) || isNaN(weight) || age <= 0 || height <= 0 || weight <= 0) {
                    bmrEl.textContent = '--';
                    tdeeEl.textContent = '--';
                    suggEl.innerHTML = '请填写完整信息';
                    return;
                }

                let bmr;
                if (gender === 'male') {
                    bmr = 10 * weight + 6.25 * height - 5 * age + 5;
                } else {
                    bmr = 10 * weight + 6.25 * height - 5 * age - 161;
                }
                const tdee = bmr * activity;
                let targetKcal = (target === 'maintain') ? tdee : (tdee - 400);
                if (targetKcal < 1000) targetKcal = 1000;

                bmrEl.textContent = Math.round(bmr);
                tdeeEl.textContent = Math.round(tdee);
                suggEl.innerHTML = '建议每日摄入 <strong>' + Math.round(targetKcal) + '</strong> 千卡 (' + (target === 'maintain' ?
                    '维持' : '减肥') + ')';
            }

            settingsSave.addEventListener('click', function() {
                const age = parseInt(document.getElementById('userAge').value);
                const gender = document.getElementById('userGenderSelect').value;
                const height = parseFloat(document.getElementById('userHeight').value);
                const weight = parseFloat(document.getElementById('userWeight').value);
                const activity = parseFloat(document.getElementById('userActivity').value);
                const target = document.querySelector('#targetGroup .active')?.dataset.target || 'lose';

                if (isNaN(age) || isNaN(height) || isNaN(weight) || age <= 0 || height <= 0 || weight <= 0) {
                    showToast('请完整填写年龄、身高、体重!', 'error');
                    return;
                }

                const settings = { age, gender, height, weight, activity, target };
                saveSettings(settings);
                closeSettings();
                computePreview();
                renderCalendar();
            });

            // ============================================================
            //  主题弹窗
            // ============================================================
            const themeModal = document.getElementById('themeModal');
            const themeToggle = document.getElementById('themeToggle');
            const themeClose = document.getElementById('themeClose');
            const themeGrid = document.getElementById('themeGrid');

            function renderThemeGrid() {
                let html = '';
                THEMES.forEach(t => {
                    const [start, end] = t.colors;
                    const active = (t.id === currentTheme) ? ' active' : '';
                    html += `<div class="theme-btn${active}" data-theme="${t.id}">
                                <div class="color-preview" style="background:linear-gradient(135deg, ${start}, ${end});"></div>
                                ${t.name}
                             </div>`;
                });
                themeGrid.innerHTML = html;

                themeGrid.querySelectorAll('.theme-btn').forEach(btn => {
                    btn.addEventListener('click', function() {
                        const themeId = this.dataset.theme;
                        applyTheme(themeId);
                        themeGrid.querySelectorAll('.theme-btn').forEach(b => b.classList.remove('active'));
                        this.classList.add('active');
                    });
                });
            }

            function openThemeModal() {
                renderThemeGrid();
                themeModal.classList.add('show');
            }

            themeToggle.addEventListener('click', openThemeModal);
            themeClose.addEventListener('click', function() {
                themeModal.classList.remove('show');
            });
            themeModal.addEventListener('click', function(e) {
                if (e.target === this) themeModal.classList.remove('show');
            });

            // ============================================================
            //  暗黑模式切换
            // ============================================================
            const DARK_MODE_KEY = 'darkMode';
            let isDarkMode = false;

            function loadDarkMode() {
                const saved = localStorage.getItem(DARK_MODE_KEY);
                if (saved !== null) {
                    return saved === 'true';
                }
                if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
                    return true;
                }
                return false;
            }

            function applyDarkMode(enabled) {
                isDarkMode = enabled;
                if (enabled) {
                    document.body.classList.add('dark-mode');
                    document.getElementById('darkModeToggle').innerHTML = '&#9728;&#65039; 关闭暗黑模式';
                } else {
                    document.body.classList.remove('dark-mode');
                    document.getElementById('darkModeToggle').innerHTML = '&#127769; 启用暗黑模式';
                }
                localStorage.setItem(DARK_MODE_KEY, enabled);
            }

            document.getElementById('darkModeToggle').addEventListener('click', function() {
                applyDarkMode(!isDarkMode);
            });

            applyDarkMode(loadDarkMode());

            // ============================================================
            //  备份弹窗
            // ============================================================
            const backupModal = document.getElementById('backupModal');
            const backupToggle = document.getElementById('backupToggle');
            const backupExportBtn = document.getElementById('backupExportBtn');
            const backupImportBtn = document.getElementById('backupImportBtn');
            const backupFileInput = document.getElementById('backupFileInput');

            function openBackupModal() {
                backupModal.classList.add('show');
            }

            function closeBackupModal() {
                backupModal.classList.remove('show');
            }

            backupToggle.addEventListener('click', openBackupModal);
            backupModal.addEventListener('click', function(e) {
                if (e.target === this) closeBackupModal();
            });
            backupExportBtn.addEventListener('click', doExport);
            backupImportBtn.addEventListener('click', function() {
                backupFileInput.click();
            });
            backupFileInput.addEventListener('change', function(e) {
                const file = e.target.files[0];
                if (file) {
                    handleImportFile(file);
                }
                e.target.value = '';
            });

            // ============================================================
            //  体重弹窗
            // ============================================================
            const weightModal = document.getElementById('weightModal');
            const weightToggle = document.getElementById('weightToggle');
            const weightClose = document.getElementById('weightClose');
            const weightSave = document.getElementById('weightSave');
            const weightInput = document.getElementById('weightInput');
            const weightTime = document.getElementById('weightTime');
            const weightJinDisplay = document.getElementById('weightJinDisplay');

            weightInput.addEventListener('input', function() {
                const val = parseFloat(this.value);
                if (!isNaN(val) && val > 0) {
                    weightJinDisplay.textContent = (val * 2).toFixed(1) + ' 斤';
                } else {
                    weightJinDisplay.textContent = '';
                }
            });

            function openWeightModal() {
                const today = getTodayStr();
                const existing = getWeightForDate(today);
                if (existing !== null) {
                    weightInput.value = existing;
                    weightJinDisplay.textContent = (existing * 2).toFixed(1) + ' 斤';
                } else {
                    weightInput.value = '';
                    weightJinDisplay.textContent = '';
                }
                weightTime.value = '自动';
                drawWeightChart();
                weightModal.classList.add('show');
            }

            function closeWeightModal() {
                weightModal.classList.remove('show');
            }

            weightToggle.addEventListener('click', openWeightModal);
            weightClose.addEventListener('click', closeWeightModal);
            weightModal.addEventListener('click', function(e) {
                if (e.target === this) closeWeightModal();
            });

            weightSave.addEventListener('click', function() {
                const val = parseFloat(weightInput.value);
                if (isNaN(val) || val <= 0) {
                    showToast('请输入有效的体重 (kg)', 'error');
                    return;
                }
                const today = getTodayStr();
                setWeightForDate(today, val);
                showToast('&#9989; 体重已保存: ' + val + ' kg (' + (val * 2).toFixed(1) + ' 斤)', 'success');
                drawWeightChart();
                renderRecordCard(today);
                closeWeightModal();
            });

            // ============================================================
            //  体重图表
            // ============================================================
            const weightChartCanvas = document.getElementById('weightChartCanvas');
            const weightNoData = document.getElementById('weightNoData');
            const weightPeriodBtns = document.querySelectorAll('#weightPeriodGroup button');
            let weightPeriod = 7;

            function drawWeightChart() {
                const canvas = weightChartCanvas;
                const ctx = canvas.getContext('2d');
                const container = document.getElementById('weightChartContainer');
                const width = container.clientWidth - 4 || 300;
                const height = 180;
                canvas.width = width * 0.98;
                canvas.height = height;
                canvas.style.width = (width * 0.98) + 'px';
                canvas.style.height = height + 'px';

                const range = getRecentRange(weightPeriod);
                const startFull = formatDateFull(range.start);
                const endFull = formatDateFull(range.end);
                const records = getWeightRecordsInRange(startFull, endFull);

                if (records.length === 0) {
                    weightNoData.style.display = 'block';
                    ctx.clearRect(0, 0, canvas.width, canvas.height);
                    return;
                }
                weightNoData.style.display = 'none';

                const padding = { top: 20, bottom: 22, left: 36, right: 16 };
                const chartWidth = canvas.width - padding.left - padding.right;
                const chartHeight = canvas.height - padding.top - padding.bottom;

                let minWeight = Infinity,
                    maxWeight = -Infinity;
                records.forEach(r => {
                    if (r.weight < minWeight) minWeight = r.weight;
                    if (r.weight > maxWeight) maxWeight = r.weight;
                });
                const rangeWeight = maxWeight - minWeight;
                const yMin = Math.max(0, minWeight - rangeWeight * 0.15);
                const yMax = maxWeight + rangeWeight * 0.15;

                ctx.clearRect(0, 0, canvas.width, canvas.height);

                ctx.strokeStyle = '#e8ecf1';
                ctx.lineWidth = 0.5;
                const ySteps = 4;
                for (let i = 0; i <= ySteps; i++) {
                    const y = padding.top + chartHeight - (i / ySteps) * chartHeight;
                    ctx.beginPath();
                    ctx.moveTo(padding.left, y);
                    ctx.lineTo(canvas.width - padding.right, y);
                    ctx.stroke();
                    const value = yMin + (i / ySteps) * (yMax - yMin);
                    ctx.fillStyle = '#888';
                    ctx.font = '9px sans-serif';
                    ctx.textAlign = 'right';
                    ctx.textBaseline = 'middle';
                    ctx.fillText(value.toFixed(1), padding.left - 4, y);
                }

                ctx.strokeStyle = '#ff7e5f';
                ctx.lineWidth = 2.5;
                ctx.beginPath();
                records.forEach((r, idx) => {
                    const x = padding.left + (idx / (records.length - 1 || 1)) * chartWidth;
                    const y = padding.top + chartHeight - ((r.weight - yMin) / (yMax - yMin)) * chartHeight;
                    if (idx === 0) ctx.moveTo(x, y);
                    else ctx.lineTo(x, y);
                });
                ctx.stroke();

                records.forEach((r, idx) => {
                    const x = padding.left + (idx / (records.length - 1 || 1)) * chartWidth;
                    const y = padding.top + chartHeight - ((r.weight - yMin) / (yMax - yMin)) * chartHeight;
                    ctx.beginPath();
                    ctx.arc(x, y, 3.5, 0, 2 * Math.PI);
                    ctx.fillStyle = '#ff7e5f';
                    ctx.fill();
                    ctx.strokeStyle = '#fff';
                    ctx.lineWidth = 1.5;
                    ctx.stroke();
                });

                let labelInterval = 1;
                if (weightPeriod > 7 && weightPeriod <= 30) labelInterval = Math.ceil(weightPeriod / 7);
                else if (weightPeriod > 30 && weightPeriod <= 90) labelInterval = Math.ceil(weightPeriod / 15);
                else if (weightPeriod > 90 && weightPeriod <= 180) labelInterval = Math.ceil(weightPeriod / 30);
                else if (weightPeriod > 180) labelInterval = Math.ceil(weightPeriod / 60);

                ctx.fillStyle = '#888';
                ctx.font = '9px sans-serif';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'top';
                records.forEach((r, idx) => {
                    if (idx % labelInterval === 0 || idx === records.length - 1) {
                        const x = padding.left + (idx / (records.length - 1 || 1)) * chartWidth;
                        const parts = r.date.split('-');
                        const label = parts[1] + '/' + parts[2];
                        ctx.fillText(label, x, canvas.height - padding.bottom + 2);
                    }
                });
            }

            weightPeriodBtns.forEach(btn => {
                btn.addEventListener('click', function() {
                    weightPeriodBtns.forEach(b => b.classList.remove('active'));
                    this.classList.add('active');
                    weightPeriod = parseInt(this.dataset.period);
                    drawWeightChart();
                });
            });

            let resizeTimer;
            window.addEventListener('resize', function() {
                clearTimeout(resizeTimer);
                resizeTimer = setTimeout(() => {
                    if (weightModal.classList.contains('show')) {
                        drawWeightChart();
                    }
                }, 200);
            });

            // ============================================================
            //  单位联动 + 事件绑定
            // ============================================================
            document.getElementById('defaultUnit').addEventListener('change', function() {
                const servingUnit = document.getElementById('servingUnit');
                if (this.value === 'ml') {
                    servingUnit.value = 'ml';
                } else {
                    servingUnit.value = 'g';
                }
                computePreview();
            });

            document.getElementById('servingUnit').addEventListener('change', computePreview);

            ['foodName', 'defaultWeight', 'energy', 'serving'].forEach(id => {
                const el = document.getElementById(id);
                el.addEventListener('input', function() {
                    computePreview();
                    saveInputCache();
                });
                el.addEventListener('change', function() {
                    computePreview();
                    saveInputCache();
                });
            });
            ['defaultUnit', 'servingUnit'].forEach(id => {
                const el = document.getElementById(id);
                el.addEventListener('change', saveInputCache);
            });

            const autoMeal = getAutoMeal();
            document.getElementById('mealSelect').value = autoMeal;

            document.getElementById('clearInputsBtn').addEventListener('click', function() {
                document.getElementById('foodName').value = '';
                document.getElementById('defaultWeight').value = '100';
                document.getElementById('energy').value = '';
                document.getElementById('serving').value = '';
                computePreview();
                saveInputCache();
            });

            document.getElementById('saveRecordBtn').addEventListener('click', saveRecord);

            document.getElementById('saveTagQuick').addEventListener('click', function() {
                const name = document.getElementById('foodName').value.trim();
                const weight = getNumberValue(document.getElementById('defaultWeight'));
                const unit = document.getElementById('defaultUnit').value;
                const energy = getNumberValue(document.getElementById('energy'));

                if (!name) { showToast('请填写食物名称', 'error'); return; }
                if (isNaN(weight) || weight <= 0) { showToast('请填写有效的每份量', 'error'); return; }
                if (isNaN(energy) || energy < 0) { showToast('请填写有效的能量 (kJ)', 'error'); return; }

                if (data.tags.length >= 50) { showToast('标签已达上限 (50)', 'warning'); return; }

                const exist = data.tags.find(t => t.name === name && t.unit === unit && t.defaultWeight === weight && t
                    .energy === energy);
                if (exist) {
                    exist.lastUsed = Date.now();
                    saveData();
                    renderTags();
                    showToast('标签已存在', 'warning');
                    return;
                }

                const sameName = data.tags.find(t => t.name === name);
                if (sameName) {
                    showConfirm('&#9888;&#65039; 确认覆盖', '已存在同名标签,是否覆盖?', '覆盖', function() {
                        sameName.defaultWeight = weight;
                        sameName.unit = unit;
                        sameName.energy = energy;
                        sameName.lastUsed = Date.now();
                        saveData();
                        renderTags();
                        showToast('&#9989; 已更新: ' + name, 'success');
                    });
                    return;
                }

                data.tags.unshift({
                    id: generateId(),
                    name: name,
                    defaultWeight: weight,
                    unit: unit,
                    energy: energy,
                    lastUsed: Date.now()
                });
                saveData();
                renderTags();
                showToast('&#9989; 已保存: ' + name, 'success');
            });

            document.getElementById('calPrev').addEventListener('click', function() {
                calMonth--;
                if (calMonth < 0) { calMonth = 11;
                    calYear--; }
                renderCalendar();
            });
            document.getElementById('calNext').addEventListener('click', function() {
                calMonth++;
                if (calMonth > 11) { calMonth = 0;
                    calYear++; }
                renderCalendar();
            });

            // ============================================================
            //  初始化
            // ============================================================
            function init() {
                applyTheme(currentTheme);
                updateDateDisplay();
                applyInputCache();
                renderCalendar();
                computePreview();

                setTimeout(() => {
                    drawWeightChart();
                }, 100);

                console.log('&#127857; 热量计算+体重记录 已启动 (紧凑日历+差值提示)');
            }

            init();

            setInterval(() => {
                const todayStr = getTodayStr();
                const todayTotal = getTotalKcalForDate(todayStr);
                const target = getTargetDisplay();
                updateResults(null, todayTotal, target);
                if (selectedDateStr === todayStr) {
                    renderRecordCard(todayStr);
                }
            }, 60000);

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

热量计算 体重记录v2.0.txt

142 KB, 下载次数: 37, 下载积分: 吾爱币 -1 CB

推荐
 楼主| liangzaihuigui 发表于 2026-7-26 00:10 |楼主
zzyblacksky 发表于 2026-7-26 00:00
食品是不是可以搜索查找啊

你是说是标签悬浮框里面的吗。因为最多50个标签,就没有搞搜索,就搞了个最近使用和最新添加排在最前面,不常用的排后面。50个标签我想够用了,不常吃的食物也没必要加入标签。

如果你说的是添加热量那里,确实没有搞,因为是单机运行的,不想联网。联网比较复杂吧,我想过联网自动填入单位能量、太复杂没搞。

有兴趣的可以再根据自己的情况修改。
沙发
shinco20 发表于 2026-7-25 20:47
3#
exclekm 发表于 2026-7-25 20:52
感谢分享!!!!
4#
mcxindi 发表于 2026-7-25 21:30
减肥救星!!!!!
5#
demonsea 发表于 2026-7-25 21:35
创作不易感谢分享
6#
anduiy 发表于 2026-7-25 21:35
这个好,源码都出来了
7#
wdnmd6668848 发表于 2026-7-25 21:56
感谢分享!
8#
adfsd 发表于 2026-7-25 22:05
厉害!在我看来就是大佬
9#
zzyblacksky 发表于 2026-7-26 00:00
食品是不是可以搜索查找啊
10#
335505 发表于 2026-7-26 00:07
我觉得现在还是稍微胖点好,做真实的自己,别把自己胃饿坏了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-9-12 02:56

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表