<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>YURI HOME Family OS Ver.6.1 Early Access</title>
<style>
:root{--bg:#f5f8f1;--panel:#fff;--green:#6f9b67;--green2:#dfead8;--text:#324033;--muted:#6f7d70;--line:#d9e4d4;--danger:#b64f48;--shadow:0 8px 24px rgba(70,90,65,.10)}
*{box-sizing:border-box}body{margin:0;font-family:"Yu Gothic","Hiragino Kaku Gothic ProN",system-ui,sans-serif;background:var(--bg);color:var(--text)}
.app{max-width:1440px;margin:auto;padding:20px}header{display:flex;gap:16px;align-items:center;justify-content:space-between;margin-bottom:16px}.brand{display:flex;align-items:center;gap:12px}.logo{width:48px;height:48px;border-radius:16px;background:var(--green2);display:grid;place-items:center;font-size:28px}h1{font-size:28px;margin:0}.sub{font-size:13px;color:var(--muted)}.date{background:var(--panel);padding:14px 18px;border-radius:16px;box-shadow:var(--shadow);font-weight:700}
nav{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0 20px}nav button,.btn{border:1px solid var(--line);background:#fff;padding:10px 16px;border-radius:12px;cursor:pointer;color:var(--text);font-weight:700}nav button.active,.btn.primary{background:var(--green);color:white;border-color:var(--green)}.page{display:none}.page.active{display:block}
.grid{display:grid;grid-template-columns:1.1fr 1.5fr 1fr;gap:16px}.panel{background:var(--panel);border:1px solid var(--line);border-radius:20px;box-shadow:var(--shadow);padding:18px}.panel h2{font-size:17px;margin:0 0 14px}.total{background:linear-gradient(145deg,#dcebd5,#eef5e9);min-height:230px}.total .big{font-size:46px;font-weight:800;margin:18px 0}.metric-list{display:grid;gap:10px}.metric-row{display:flex;justify-content:space-between;border-bottom:1px dashed var(--line);padding-bottom:8px}.recommend{min-height:230px;display:grid;grid-template-columns:1fr auto;align-items:center}.recommend .method{font-size:42px;font-weight:800;color:#197c48}.reason{line-height:1.9}.next-card{background:linear-gradient(145deg,#fff7f4,#fff);min-height:230px}.badge{display:inline-block;padding:5px 9px;border-radius:999px;background:#f6c4bb;color:#8f342f;font-size:12px;font-weight:800}
.cards{display:grid;grid-template-columns:repeat(5,1fr);gap:14px;margin-top:16px}.money-card{background:#fff;border:1px solid var(--line);border-radius:18px;padding:15px;box-shadow:var(--shadow)}.money-card .name{font-size:14px;color:var(--muted)}.money-card .amount{font-size:25px;font-weight:800;margin:8px 0}.money-card button{width:100%;border:1px solid #bfd1b8;background:#edf5e9;border-radius:10px;padding:8px;cursor:pointer}.two{display:grid;grid-template-columns:1.4fr 1fr;gap:16px;margin-top:16px}.week{display:grid;gap:10px}.week-row{display:grid;grid-template-columns:160px 1fr 90px;gap:12px;align-items:center}.bar{height:12px;background:#edf1eb;border-radius:99px;overflow:hidden}.bar span{display:block;height:100%;background:#9ec694}.flow{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap}.flow .node{flex:1;min-width:92px;text-align:center;background:#f7faf5;border:1px solid var(--line);border-radius:14px;padding:14px}.arrow{font-size:24px;color:#7fa973}
table{width:100%;border-collapse:collapse;background:#fff}th,td{padding:11px;border-bottom:1px solid var(--line);text-align:left}th{background:#eff5ec;font-size:13px}td.num{text-align:right;font-variant-numeric:tabular-nums}.negative{color:var(--danger);font-weight:700}.positive{color:#2a7b48;font-weight:700}.paid{opacity:.55;text-decoration:line-through}.mini{padding:6px 9px;border-radius:9px;border:1px solid var(--line);background:#fff;cursor:pointer}.form-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:14px}input,select{width:100%;padding:10px;border:1px solid var(--line);border-radius:10px;background:#fff}.small{font-size:12px;color:var(--muted)}.notice{margin-top:16px;background:#eef5e9;border-radius:14px;padding:12px 15px}
@media(max-width:1100px){.grid{grid-template-columns:1fr}.cards{grid-template-columns:repeat(3,1fr)}.two{grid-template-columns:1fr}.form-grid{grid-template-columns:repeat(2,1fr)}}@media(max-width:650px){.app{padding:12px}.cards{grid-template-columns:repeat(2,1fr)}.week-row{grid-template-columns:120px 1fr 70px}h1{font-size:22px}.total .big{font-size:38px}}.quick-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
.quick{border:1px solid var(--line);background:#fff;border-radius:14px;padding:12px;cursor:pointer;text-align:left}
.quick b{display:block;margin-bottom:4px}
.report-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.category-row{display:grid;grid-template-columns:110px 1fr 90px;gap:10px;align-items:center;margin:10px 0}
.category-bar{height:12px;border-radius:99px;background:#edf1eb;overflow:hidden}
.category-bar span{display:block;height:100%;background:#9ec694}
.toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px}
.toolbar input,.toolbar select{width:auto;min-width:150px}
.empty{text-align:center;padding:26px;color:var(--muted)}
@media(max-width:760px){nav{position:sticky;bottom:0;z-index:20;background:rgba(245,248,241,.96);padding:8px 0;margin-top:10px}.quick-grid{grid-template-columns:repeat(2,1fr)}.report-grid{grid-template-columns:1fr}}
@media print{nav,.btn,.form-grid,.quick-grid,.toolbar{display:none!important}.page{display:block!important;page-break-after:always}.app{max-width:none;padding:0}.panel,.money-card{box-shadow:none}}

.hero-grid{display:grid;grid-template-columns:1.35fr 1fr;gap:16px}
.hero-card{background:linear-gradient(145deg,#dcebd5,#f8fbf5);border:1px solid var(--line);border-radius:24px;padding:22px;box-shadow:var(--shadow)}
.hero-title{font-size:14px;color:var(--muted);font-weight:700}
.hero-amount{font-size:48px;font-weight:900;letter-spacing:-1px;margin:8px 0}
.hero-sub{display:flex;gap:10px;flex-wrap:wrap}
.pill{display:inline-flex;align-items:center;gap:6px;padding:7px 10px;border-radius:999px;background:#fff;border:1px solid var(--line);font-size:12px;font-weight:700}
.home-section{margin-top:16px}
.home-grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
.home-tile{background:#fff;border:1px solid var(--line);border-radius:18px;padding:15px;box-shadow:var(--shadow);cursor:pointer}
.home-tile:hover{transform:translateY(-1px)}
.home-tile .label{font-size:12px;color:var(--muted)}
.home-tile .value{font-size:22px;font-weight:800;margin-top:6px}
.ai-card{background:linear-gradient(145deg,#fffaf0,#fff);border:1px solid #eadfca}
.ai-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
.ai-message{line-height:1.8;margin-top:10px}
.alert-list{display:grid;gap:9px}
.alert-item{display:flex;gap:10px;align-items:flex-start;padding:10px;border-radius:12px;background:#f7faf5}
.alert-icon{width:32px;height:32px;border-radius:10px;background:#e5efe0;display:grid;place-items:center;flex:0 0 auto}
.mission-list{display:grid;gap:8px}
.mission{display:flex;align-items:center;gap:10px;padding:10px;border:1px solid var(--line);border-radius:12px;background:#fff}
.mission input{width:auto}
.progress-ring{width:86px;height:86px;border-radius:50%;display:grid;place-items:center;background:conic-gradient(#7fa973 var(--progress),#e9eee6 0);position:relative}
.progress-ring:after{content:"";width:64px;height:64px;border-radius:50%;background:#fff;position:absolute}
.progress-ring b{position:relative;z-index:1;font-size:20px}
.next-debit{display:grid;gap:8px}
.debit-row{display:grid;grid-template-columns:1fr auto;gap:10px;padding:10px 0;border-bottom:1px dashed var(--line)}
.consistency-ok{background:#e9f5e8;color:#2f6f3c;padding:10px 12px;border-radius:12px;font-weight:700}
.consistency-warn{background:#fff0e8;color:#9c4e28;padding:10px 12px;border-radius:12px;font-weight:700}
@media(max-width:980px){.hero-grid{grid-template-columns:1fr}.home-grid-4{grid-template-columns:repeat(2,1fr)}}
@media(max-width:580px){.home-grid-4{grid-template-columns:1fr 1fr}.hero-amount{font-size:38px}.hero-card{padding:18px}}


:root{
  --bg:#f4f9fb;
  --card:#fbfefe;
  --text:#31464f;
  --muted:#6c8591;
  --line:#dce9ed;
  --accent:#77aa9a;
  --accent2:#9fc7d8;
  --soft:#e8f4ef;
  --softblue:#eaf5fa;
  --shadow:0 10px 30px rgba(78,110,120,.10);
}
body{background:linear-gradient(180deg,#eef7fb 0%,#f7fbfc 38%,#eef7f2 100%);}
.panel,.money-card,.home-tile,.hero-card{border-color:var(--line)}
.btn{background:linear-gradient(135deg,#7eb2a3,#86bfd1)}
.fab{position:fixed;right:22px;bottom:84px;width:62px;height:62px;border-radius:50%;border:none;background:linear-gradient(135deg,#78aa9a,#8abfd0);color:#fff;font-size:34px;box-shadow:0 14px 30px rgba(68,118,128,.28);cursor:pointer;z-index:50}
.modal-backdrop{display:none;position:fixed;inset:0;background:rgba(35,57,67,.34);backdrop-filter:blur(4px);z-index:90;padding:18px;align-items:flex-end;justify-content:center}
.modal-backdrop.show{display:flex}
.sheet{width:min(720px,100%);max-height:88vh;overflow:auto;background:#fbfefe;border:1px solid var(--line);border-radius:26px 26px 16px 16px;padding:20px;box-shadow:0 24px 70px rgba(40,70,80,.28)}
.sheet h2{margin-top:0}
.choice-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.choice{border:1px solid var(--line);background:#fff;border-radius:16px;padding:13px;text-align:left;cursor:pointer}
.choice.selected{background:linear-gradient(135deg,#e6f3ee,#e9f5fa);border-color:#8bb8ac;box-shadow:0 0 0 2px rgba(123,173,158,.16)}
.choice b{display:block;margin-bottom:4px}
.step-label{font-size:12px;color:var(--muted);font-weight:800;letter-spacing:.04em;margin:16px 0 8px}
.other-fields{display:none;gap:8px;margin-top:8px}
.other-fields.show{display:grid}
.favorite-summary{background:linear-gradient(135deg,#edf7f3,#edf7fb);border:1px solid var(--line);border-radius:16px;padding:12px;margin-top:14px}
.favorite-list{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
.favorite-card{border:1px solid var(--line);background:#fff;border-radius:16px;padding:13px;cursor:pointer}
.favorite-card .fav-title{font-weight:800}
.favorite-card .fav-meta{font-size:12px;color:var(--muted);margin-top:4px}
.modal-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:16px;flex-wrap:wrap}
@media(max-width:620px){.choice-grid{grid-template-columns:repeat(2,1fr)}.favorite-list{grid-template-columns:1fr}.sheet{padding:16px}.fab{right:16px;bottom:78px}}


/* Ver.6.1 iPhone-first purchase entry */
.mobile-entry{display:none;position:fixed;inset:0;background:#f5fafb;z-index:120;overflow:auto;padding-bottom:calc(24px + env(safe-area-inset-bottom))}
.mobile-entry.show{display:block}
.mobile-entry-head{position:sticky;top:0;z-index:2;display:flex;align-items:center;justify-content:space-between;padding:14px 16px;background:rgba(245,250,251,.94);backdrop-filter:blur(14px);border-bottom:1px solid var(--line)}
.mobile-entry-head h2{margin:0;font-size:19px}
.mobile-entry-body{max-width:620px;margin:auto;padding:16px}
.entry-step{display:none;animation:entryFade .18s ease}
.entry-step.active{display:block}
@keyframes entryFade{from{opacity:.25;transform:translateY(5px)}to{opacity:1;transform:none}}
.entry-title{font-size:22px;font-weight:900;margin:6px 0 4px}
.entry-help{color:var(--muted);font-size:13px;margin-bottom:16px}
.tap-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:11px}
.tap-card{min-height:84px;border:1px solid var(--line);border-radius:20px;background:#fff;padding:14px;text-align:left;box-shadow:0 7px 18px rgba(65,100,110,.07);cursor:pointer;color:var(--text)}
.tap-card:active{transform:scale(.98)}
.tap-card .tap-icon{font-size:26px;display:block;margin-bottom:5px}
.tap-card strong{font-size:15px}
.tap-card small{display:block;color:var(--muted);margin-top:3px}
.tap-card.recommended{background:linear-gradient(135deg,#e7f4ee,#e9f5fa);border-color:#89b8aa}
.tap-card.wide{grid-column:1/-1;min-height:68px}
.recent-strip{display:flex;gap:9px;overflow:auto;padding:2px 0 12px;scrollbar-width:none}
.recent-chip{flex:0 0 auto;border:1px solid var(--line);background:#fff;border-radius:999px;padding:10px 13px;cursor:pointer;font-weight:800}
.number-wrap{background:#fff;border:1px solid var(--line);border-radius:24px;padding:18px;box-shadow:var(--shadow)}
.number-display{font-size:38px;font-weight:900;text-align:right;min-height:54px;letter-spacing:.02em}
.number-display span{font-size:20px;margin-left:4px}
.num-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:14px}
.num-key{height:58px;border:none;border-radius:18px;background:#eef5f7;font-size:22px;font-weight:900;color:var(--text);cursor:pointer}
.num-key.action{background:#e5f1ec;font-size:16px}
.num-key:active{transform:scale(.97)}
.entry-summary{background:linear-gradient(135deg,#eaf5f0,#eaf5fa);border:1px solid var(--line);border-radius:20px;padding:15px;margin-bottom:14px;line-height:1.8}
.entry-save{width:100%;border:none;border-radius:19px;padding:16px;background:linear-gradient(135deg,#6fae9a,#82bcd0);color:#fff;font-size:18px;font-weight:900;cursor:pointer;box-shadow:0 12px 24px rgba(77,133,143,.22)}
.entry-back{border:1px solid var(--line);background:#fff;border-radius:12px;padding:8px 12px;font-weight:800;color:var(--text)}
.custom-entry{display:grid;gap:9px;margin-top:12px}
.step-dots{display:flex;gap:6px;justify-content:center;padding:10px}
.step-dot{width:7px;height:7px;border-radius:50%;background:#d7e4e7}
.step-dot.active{width:22px;border-radius:999px;background:#77ad9e}
.quick-start-box{background:linear-gradient(135deg,#e9f5f0,#edf7fb);border:1px solid var(--line);border-radius:20px;padding:14px;margin-bottom:15px}
@media(max-width:650px){
  body{padding-bottom:76px}
  header .date{display:none}
  nav{position:fixed;bottom:0;left:0;right:0;z-index:40;margin:0;padding:7px 8px calc(7px + env(safe-area-inset-bottom));background:rgba(251,254,254,.95);backdrop-filter:blur(14px);border-top:1px solid var(--line);display:grid;grid-template-columns:repeat(6,1fr);gap:3px}
  nav button{padding:8px 2px;border:0;background:transparent;font-size:10px;border-radius:11px}
  nav button.active{background:#e5f2ed;color:var(--text)}
  .fab{bottom:calc(76px + env(safe-area-inset-bottom));width:66px;height:66px}
  .mobile-entry-body{padding:13px}
}

</style>

<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#A8CDB8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Family OS">
<link rel="apple-touch-icon" href="icons/apple-touch-icon.png">

</head>
<body><div class="app">
<header><div class="brand"><div class="logo">🌿</div><div><h1>YURI HOME Family OS <span class="small">Ver.6.1 Early Access</span></h1><div class="sub">30秒で家のお金がわかる Daily Dashboard</div></div></div><div class="date" id="todayLabel">—</div></header>
<div class="panel" style="margin-bottom:16px;padding:12px 16px"><div style="display:flex;gap:18px;flex-wrap:wrap;align-items:center;justify-content:space-between"><div><b>家計健康度</b> <span id="healthScore" style="font-size:22px;font-weight:800">—</span></div><div><b>今週残り</b> <span id="currentWeekRemain">2,500円</span></div><div><b>次回引落</b> <span id="nextDebitSummary">楽天カード 34,169円</span></div><div><b>最終更新</b> <span id="lastUpdated">—</span></div></div></div>
<nav><button class="active" data-page="home">🏠 HOME</button><button data-page="passbooks">📖 通帳</button><button data-page="cardsPage">💳 カード</button><button data-page="envelopes">✉️ 封筒</button><button data-page="report">📊 レポート</button><button onclick="undoLast()">↶ 元に戻す</button><button onclick="exportData()">バックアップ</button><button onclick="document.getElementById('importFile').click()">復元</button><input id="importFile" type="file" accept="application/json" style="display:none" onchange="importData(event)"></nav>
<section id="home" class="page active">
  <div class="hero-grid">
    <div class="hero-card">
      <div class="hero-title">今月使えるお金</div>
      <div class="hero-amount" id="usableTotal">—</div>
      <div class="hero-sub">
        <span class="pill">✉️ 封筒 <b id="envTotalTop">—</b></span>
        <span class="pill">📱 電子マネー <b id="emoneyTotalTop">—</b></span>
        <span class="pill">👛 財布 <b id="walletTop">—</b></span>
      </div>
      <div style="margin-top:16px">
        <div class="small">今月の進み具合</div>
        <div class="bar" style="height:14px;margin-top:7px"><span id="monthProgressBar" style="width:0%"></span></div>
        <div class="small" id="monthProgressText" style="margin-top:6px">—</div>
      </div>
    </div>

    <div class="panel ai-card">
      <div class="ai-head">
        <div>
          <div class="small">🤖 Family OS ナビ</div>
          <div class="method" id="recommendedMethod">—</div>
        </div>
        <div style="font-size:64px">🐰</div>
      </div>
      <div class="ai-message" id="recommendReason">残高状況を確認しています。</div>
      <div class="notice" id="aiDailyComment">今日の家計コメントを作成しています。</div>
    </div>
  </div>

  <div class="home-section home-grid-4" id="homeBalanceTiles"></div>
  <div class="home-section panel">
    <div style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap">
      <div>
        <h2 style="margin-bottom:4px">かんたん購入入力</h2>
        <div class="small">購入のたびに、お店・支払い方法・金額をタップして入力できます。</div>
      </div>
      <button class="btn" onclick="openPurchaseEntry()">＋ 購入を入力</button>
    </div>
  </div>

  <div class="home-section two">
    <div class="panel">
      <h2>今週の予算</h2>
      <div style="display:flex;gap:18px;align-items:center;flex-wrap:wrap">
        <div class="progress-ring" id="weekRing" style="--progress:0%"><b id="weekPercent">0%</b></div>
        <div style="flex:1;min-width:220px">
          <div class="small" id="currentWeekLabel">—</div>
          <div style="font-size:34px;font-weight:900;margin:4px 0" id="currentWeekAmount">—</div>
          <div class="small" id="currentWeekUsed">—</div>
        </div>
      </div>
      <div class="notice" id="weekAdvice">週予算を確認しています。</div>
    </div>

    <div class="panel">
      <h2>次のカード引き落とし</h2>
      <div class="next-debit" id="homeDebitList"></div>
      <button class="btn" onclick="document.querySelector('[data-page=&quot;cardsPage&quot;]').click()">カード管理を開く</button>
    </div>
  </div>

  <div class="home-section report-grid">
    <div class="panel">
      <h2>今日のお金の流れ</h2>
      <div class="flow" id="todayFlow"></div>
      <div class="notice" id="todayFlowNote">本日の取引はまだありません。</div>
    </div>

    <div class="panel">
      <h2>今日のミッション</h2>
      <div class="mission-list">
        <label class="mission"><input type="checkbox" id="mission1" onchange="saveMissions()"> 現金をなるべく使わない</label>
        <label class="mission"><input type="checkbox" id="mission2" onchange="saveMissions()"> 支出を1件入力する</label>
        <label class="mission"><input type="checkbox" id="mission3" onchange="saveMissions()"> カード引落を確認する</label>
      </div>
    </div>
  </div>

  <div class="home-section report-grid">
    <div class="panel">
      <h2>お知らせ</h2>
      <div class="alert-list" id="homeAlerts"></div>
    </div>
    <div class="panel">
      <h2>データチェック</h2>
      <div id="consistencyBox">確認しています。</div>
      <p class="small">資金移動は支出に含めず、残高と通帳の整合性を確認します。</p>
    </div>
  </div>
</section>
<section id="passbooks" class="page">
  <div class="panel" style="margin-bottom:16px">
    <h2>かんたん入力</h2>
    <div class="quick-grid">
      <button class="quick" onclick="quickEntry('paypay','自販機',150,'食費','飲み物')"><b>🥤 自販機</b><span class="small">PayPay・150円</span></button>
      <button class="quick" onclick="quickEntry('wallet','スーパー',1000,'食費','金額を変更してください')"><b>🛒 スーパー</b><span class="small">財布・1,000円</span></button>
      <button class="quick" onclick="quickEntry('suica','交通利用',0,'交通','金額を入力してください')"><b>🚃 交通</b><span class="small">Suica</span></button>
      <button class="quick" onclick="quickEntry('paypay','家族の飲み物',600,'食費','150円×4人分')"><b>👨‍👩‍👧‍👧 家族分</b><span class="small">PayPay・600円</span></button>
    </div>
  </div><div class="panel"><h2>通帳入力</h2><div class="form-grid"><input id="txDate" type="date" value="2026-07-26"><select id="txAccount"></select><input id="txDesc" placeholder="摘要"><select id="txType"><option value="out">出金</option><option value="in">入金</option><option value="transfer">資金移動</option></select><input id="txAmount" type="number" placeholder="金額"><button class="btn primary" onclick="addTransaction()">追加</button></div></div><div class="panel" style="margin-top:16px">
      <h2>取引一覧</h2>
      <div class="toolbar">
        <select id="historyAccount" onchange="renderHistory()"><option value="all">すべての口座</option></select>
        <select id="historyCategory" onchange="renderHistory()"><option value="all">すべての区分</option></select>
        <input id="historySearch" placeholder="摘要・メモを検索" oninput="renderHistory()">
        <button class="mini" onclick="clearHistoryFilter()">絞り込み解除</button>
      </div>
      <div id="historyWrap"></div>
    </div>
    <div id="passbookWrap" style="display:grid;gap:16px;margin-top:16px"></div></section>
<section id="cardsPage" class="page"><div class="panel"><h2>カード引き落とし予定</h2><table><thead><tr><th>カード</th><th>引落日</th><th>請求額</th><th>引落後残高</th><th>操作</th></tr></thead><tbody><tr><td>楽天カード</td><td>7/27</td><td class="num">34,169円</td><td class="num">349,916円</td></tr><tr><td>イオンカード</td><td>8/2</td><td class="num">74,547円</td><td class="num">275,369円</td></tr><tr><td>JCBカード</td><td>8/10</td><td class="num">1,281円</td><td class="num">274,088円</td></tr><tr><td>VIEWカード</td><td>8/25</td><td class="num">32,415円</td><td class="num">241,673円</td></tr></tbody></table></div></section>
<section id="envelopes" class="page"><div class="panel"><h2>封筒管理</h2><div id="envelopeTable"></div></div></section>
<section id="report" class="page">
  <div class="report-grid">
    <div class="panel">
      <h2>月次サマリー</h2>
      <div class="cards" style="grid-template-columns:repeat(2,1fr)">
        <div class="money-card"><div class="name">家計健康度</div><div class="amount" id="reportHealth">—</div><div class="small">残高と予算から判定</div></div>
        <div class="money-card"><div class="name">記録済み支出</div><div class="amount" id="reportExpense">—</div><div class="small">資金移動を除く</div></div>
        <div class="money-card"><div class="name">記録済み入金</div><div class="amount" id="reportIncome">—</div><div class="small">通常入金の合計</div></div>
        <div class="money-card"><div class="name">電子マネー残高</div><div class="amount" id="reportEMoney">—</div><div class="small">5種類の合計</div></div>
      </div>
      <div class="notice" id="reportComment">集計しています。</div>
    </div>
    <div class="panel">
      <h2>支出カテゴリー</h2>
      <div id="categoryReport"></div>
    </div>
  </div>
  <div class="panel" style="margin-top:16px">
    <h2>支払い元別の支出</h2>
    <div id="accountReport"></div>
  </div>
  <div class="panel" style="margin-top:16px">
    <h2>データ管理</h2>
    <p class="small">定期的にバックアップしておくと、別のパソコンやブラウザでも復元できます。</p>
    <button class="btn" onclick="exportData()">バックアップを保存</button>
    <button class="btn" onclick="document.getElementById('importFile').click()">バックアップを復元</button>
    <button class="btn" onclick="if(confirm('試作データへ戻しますか？現在の入力内容は消えます。')){localStorage.removeItem('familyOSState');localStorage.removeItem('familyOSUndo');location.reload()}">試作データへ戻す</button>
  </div>
</section>
</div>
<script>
const initial={balances:{bank:{name:"銀行",icon:"🏦",amount:384085},wallet:{name:"財布（現金）",icon:"👛",amount:24000},envelopes:{name:"封筒合計",icon:"✉️",amount:123500},paypay:{name:"PayPay",icon:"📱",amount:15278},suica:{name:"Suica",icon:"🚃",amount:875},quo:{name:"QUO Pay",icon:"🎟️",amount:69},aeon:{name:"イオンPay",icon:"💳",amount:23},rakuten:{name:"楽天Pay",icon:"🔴",amount:0},reimbursement:{name:"立替封筒",icon:"📦",amount:5000}},weeks:[{name:"第1週（7/25〜7/27）",budget:2500,remain:2500},{name:"第2週（7/28〜8/3）",budget:29000,remain:29000},{name:"第3週（8/4〜8/10）",budget:29000,remain:29000},{name:"第4週（8/11〜8/17）",budget:29000,remain:29000},{name:"第5週（8/18〜8/24）",budget:29000,remain:29000},{name:"第6週（8/25〜8/31）",budget:5000,remain:5000}],cards:[
    {name:"楽天カード",date:"2026-07-27",amount:34169,paid:false},
    {name:"イオンカード",date:"2026-08-02",amount:74547,paid:false},
    {name:"JCBカード",date:"2026-08-10",amount:1281,paid:false},
    {name:"VIEWカード",date:"2026-08-25",amount:32415,paid:false}
  ],settings:{weeklyBudgetEnabled:true},tx:[{date:"7/24",account:"bank",desc:"前月終了残高",in:0,out:0,balance:174494},{date:"7/25",account:"bank",desc:"給与",in:280091,out:0,balance:454585},{date:"7/25",account:"bank",desc:"前月封筒戻り",in:103000,out:0,balance:557585},{date:"7/25",account:"bank",desc:"M銀行へ振替",in:0,out:50000,balance:507585},{date:"7/25",account:"bank",desc:"生活費引き出し",in:0,out:123500,balance:384085},{date:"7/26",account:"wallet",desc:"コープ封筒へ移動",in:0,out:5000,balance:24000},{date:"7/26",account:"paypay",desc:"開始残高",in:0,out:0,balance:15878},{date:"7/26",account:"paypay",desc:"自販機（家族4人分）",in:0,out:600,balance:15278},{date:"7/25",account:"suica",desc:"開始残高",in:0,out:0,balance:875}]};
let state=JSON.parse(localStorage.getItem("familyOSState")||"null")||initial;const yen=n=>Number(n).toLocaleString("ja-JP")+"円";function save(){localStorage.setItem("familyOSState",JSON.stringify(state));localStorage.setItem("familyOSLastUpdated",new Date().toLocaleString("ja-JP"))}


function snapshot(){
  localStorage.setItem("familyOSUndo",JSON.stringify(state));
}
function undoLast(){
  const raw=localStorage.getItem("familyOSUndo");
  if(!raw)return alert("元に戻せる操作がありません");
  if(!confirm("直前の操作を元に戻しますか？"))return;
  const current=JSON.stringify(state);
  state=JSON.parse(raw);
  localStorage.setItem("familyOSUndo",current);
  save();render();
}
function quickEntry(account,desc,amount,category,memo){
  document.querySelector('[data-page="passbooks"]').click();
  txAccount.value=account;
  txType.value="out";
  toggleTransfer();
  txDesc.value=desc;
  txCategory.value=category;
  txAmount.value=amount||"";
  txMemo.value=memo||"";
  txAmount.focus();
}
function clearHistoryFilter(){
  historyAccount.value="all";historyCategory.value="all";historySearch.value="";renderHistory();
}
function renderHistory(){
  if(!document.getElementById("historyWrap"))return;
  const account=historyAccount.value||"all",cat=historyCategory.value||"all",q=(historySearch.value||"").toLowerCase();
  const rows=[...state.tx].reverse().filter(t=>
    (account==="all"||t.account===account) &&
    (cat==="all"||(t.category||"—")===cat) &&
    (!q||`${t.desc} ${t.memo||""}`.toLowerCase().includes(q))
  );
  historyWrap.innerHTML=rows.length?`<table><thead><tr><th>日付</th><th>口座</th><th>摘要</th><th>区分</th><th>金額</th></tr></thead><tbody>${rows.map(t=>{
    const amount=t.out?`<span class="negative">−${yen(t.out)}</span>`:`<span class="positive">＋${yen(t.in)}</span>`;
    return `<tr><td>${t.date}</td><td>${state.balances[t.account]?.name||t.account}</td><td>${t.desc}${t.memo?`<div class="small">${t.memo}</div>`:""}</td><td>${t.category||"—"}</td><td class="num">${amount}</td></tr>`
  }).join("")}</tbody></table>`:`<div class="empty">該当する取引はありません。</div>`;
}
function renderReport(){
  const ignored=["資金移動","立替・精算","繰越"];
  const expenseRows=state.tx.filter(t=>t.out>0&&!ignored.includes(t.category));
  const incomeRows=state.tx.filter(t=>t.in>0&&!ignored.includes(t.category));
  const totalExpense=expenseRows.reduce((s,t)=>s+t.out,0);
  const totalIncome=incomeRows.reduce((s,t)=>s+t.in,0);
  const b=state.balances;
  const em=b.paypay.amount+b.suica.amount+b.quo.amount+b.aeon.amount+b.rakuten.amount;
  reportHealth.textContent=healthScore.textContent;
  reportExpense.textContent=yen(totalExpense);
  reportIncome.textContent=yen(totalIncome);
  reportEMoney.textContent=yen(em);

  const byCat={};
  expenseRows.forEach(t=>byCat[t.category||"その他"]=(byCat[t.category||"その他"]||0)+t.out);
  const maxCat=Math.max(1,...Object.values(byCat));
  categoryReport.innerHTML=Object.keys(byCat).length?Object.entries(byCat).sort((a,b)=>b[1]-a[1]).map(([k,v])=>`<div class="category-row"><b>${k}</b><div class="category-bar"><span style="width:${v/maxCat*100}%"></span></div><div class="num">${yen(v)}</div></div>`).join(""):`<div class="empty">支出データがありません。</div>`;

  const byAccount={};
  expenseRows.forEach(t=>byAccount[t.account]=(byAccount[t.account]||0)+t.out);
  accountReport.innerHTML=Object.keys(byAccount).length?`<table><thead><tr><th>支払い元</th><th>支出額</th><th>割合</th></tr></thead><tbody>${Object.entries(byAccount).sort((a,b)=>b[1]-a[1]).map(([k,v])=>`<tr><td>${b[k]?.icon||""} ${b[k]?.name||k}</td><td class="num">${yen(v)}</td><td class="num">${totalExpense?Math.round(v/totalExpense*100):0}%</td></tr>`).join("")}</tbody></table>`:`<div class="empty">支出データがありません。</div>`;

  const remain=state.weeks.reduce((s,w)=>s+w.remain,0);
  reportComment.textContent=remain>=0
    ?`週予算は合計 ${yen(remain)} 残っています。資金移動は支出集計から除外されています。`
    :`週予算を ${yen(Math.abs(remain))} 超過しています。支出内容を確認しましょう。`;
}

function normalizeState(){if(!state.cards)state.cards=[{name:"楽天カード",date:"2026-07-27",amount:34169,paid:false},{name:"イオンカード",date:"2026-08-02",amount:74547,paid:false},{name:"JCBカード",date:"2026-08-10",amount:1281,paid:false},{name:"VIEWカード",date:"2026-08-25",amount:32415,paid:false}];if(!state.settings)state.settings={weeklyBudgetEnabled:true};state.tx.forEach(t=>{if(!t.createdAt)t.createdAt=new Date().toISOString()})}
function parseDateText(text){const [m,d]=text.split("/").map(Number);return new Date(2026,m-1,d)}
function currentWeekIndex(dateText){const d=parseDateText(dateText),r=[[new Date(2026,6,25),new Date(2026,6,27)],[new Date(2026,6,28),new Date(2026,7,3)],[new Date(2026,7,4),new Date(2026,7,10)],[new Date(2026,7,11),new Date(2026,7,17)],[new Date(2026,7,18),new Date(2026,7,24)],[new Date(2026,7,25),new Date(2026,7,31)]];return r.findIndex(([s,e])=>d>=s&&d<=e)}
function updateRecommendation(){const b=state.balances;let method="PayPay",reasons=[];if(b.paypay.amount>=1000){method="PayPay";reasons=["✓ 現金を温存できます","✓ 少額決済に向いています",`✓ 残高 ${yen(b.paypay.amount)} あります`]}else if(b.suica.amount>=500){method="Suica";reasons=["✓ 交通・コンビニで使えます","✓ PayPay残高が少なめです",`✓ 残高 ${yen(b.suica.amount)} あります`]}else{method="財布";reasons=["✓ 電子マネー残高が少なめです","✓ 必要な分だけ現金を使いましょう",`✓ 財布残高 ${yen(b.wallet.amount)} です`]}recommendedMethod.textContent=method;recommendReason.innerHTML=reasons.join("<br>")}
function updateTodayFlow(){const rows=state.tx.filter(t=>t.out>0).slice(-1);if(!rows.length){todayFlow.innerHTML="";todayFlowNote.textContent="本日の取引はまだありません。";return}const t=rows[0],a=state.balances[t.account];todayFlow.innerHTML=`<div class="node">${a.icon}<br>${a.name}</div><div class="arrow">→</div><div class="node">🧾<br>${t.desc}</div>`;todayFlowNote.innerHTML=`${t.date}　${t.desc}　<span class="negative">−${yen(t.out)}</span>`}
function renderCards(){let running=state.balances.bank.amount;cardScheduleBody.innerHTML=state.cards.map((c,i)=>{if(!c.paid)running-=c.amount;return `<tr class="${c.paid?"paid":""}"><td>${c.name}</td><td>${c.date.slice(5).replace("-","/")}</td><td class="num">${yen(c.amount)}</td><td class="num">${yen(running)}</td><td><button class="mini" onclick="applyCardDebit(${i})">${c.paid?"反映済":"引落反映"}</button></td></tr>`}).join("")}
function applyCardDebit(i){const c=state.cards[i];if(c.paid)return alert("すでに反映済みです");if(state.balances.bank.amount<c.amount)return alert("銀行残高が不足しています");snapshot();state.balances.bank.amount-=c.amount;state.tx.push({date:c.date.slice(5).replace("-","/"),account:"bank",desc:c.name+"引き落とし",category:"固定費",memo:"カード引き落とし",in:0,out:c.amount,balance:state.balances.bank.amount,createdAt:new Date().toISOString()});c.paid=true;save();render();alert(c.name+"の引き落としを反映しました")}
function updateHealth(){const unpaid=state.cards.filter(c=>!c.paid).reduce((s,c)=>s+c.amount,0);let score=100;if(state.balances.bank.amount<unpaid)score-=25;if(state.balances.wallet.amount<5000)score-=10;if(state.weeks.some(w=>w.remain<0))score-=20;healthScore.textContent=Math.max(0,score)+"点";const idx=Math.max(0,currentWeekIndex(txDate.value.slice(5).replace("-","/")));currentWeekRemain.textContent=yen(state.weeks[idx]?.remain||0);const next=state.cards.find(c=>!c.paid);nextDebitSummary.textContent=next?`${next.name} ${yen(next.amount)}`:"すべて反映済み";lastUpdated.textContent=localStorage.getItem("familyOSLastUpdated")||"未保存"}


function saveMissions(){
  const missions={
    m1:mission1.checked,m2:mission2.checked,m3:mission3.checked,
    date:new Date().toISOString().slice(0,10)
  };
  localStorage.setItem("familyOSMissions",JSON.stringify(missions));
}
function loadMissions(){
  const today=new Date().toISOString().slice(0,10);
  let m={m1:false,m2:false,m3:false,date:today};
  try{const saved=JSON.parse(localStorage.getItem("familyOSMissions")||"null");if(saved&&saved.date===today)m=saved}catch(e){}
  mission1.checked=!!m.m1;mission2.checked=!!m.m2;mission3.checked=!!m.m3;
}
function renderHome(){
  const b=state.balances;
  const em=b.paypay.amount+b.suica.amount+b.quo.amount+b.aeon.amount+b.rakuten.amount;
  const usable=b.envelopes.amount+em;
  usableTotal.textContent=yen(usable);
  envTotalTop.textContent=yen(b.envelopes.amount);
  emoneyTotalTop.textContent=yen(em);
  walletTop.textContent=yen(b.wallet.amount);

  const cycleStart=new Date(2026,6,25),cycleEnd=new Date(2026,7,24),now=new Date();
  const pct=Math.max(0,Math.min(100,Math.round((now-cycleStart)/(cycleEnd-cycleStart)*100)));
  monthProgressBar.style.width=pct+"%";
  monthProgressText.textContent=`家計期間 7/25〜8/24｜${pct}% 経過`;

  const tileOrder=["bank","wallet","paypay","suica","reimbursement","quo","aeon","rakuten"];
  homeBalanceTiles.innerHTML=tileOrder.map(k=>`<div class="home-tile" onclick="openBook('${k}')"><div class="label">${b[k].icon} ${b[k].name}</div><div class="value">${yen(b[k].amount)}</div></div>`).join("");

  const todayText=new Date().toLocaleDateString("ja-JP",{month:"numeric",day:"numeric"});
  const wi=Math.max(0,currentWeekIndex(todayText));
  const w=state.weeks[wi]||state.weeks[0];
  const used=Math.max(0,w.budget-w.remain);
  const remainPct=Math.max(0,Math.min(100,Math.round(w.remain/w.budget*100)));
  weekRing.style.setProperty("--progress",remainPct+"%");
  weekPercent.textContent=remainPct+"%";
  currentWeekLabel.textContent=w.name;
  currentWeekAmount.textContent=yen(w.remain);
  currentWeekUsed.textContent=`予算 ${yen(w.budget)}｜使用 ${yen(used)}`;
  weekAdvice.textContent=w.remain<0?"予算を超えています。支出内容を確認しましょう。":remainPct<30?"残りが少なくなっています。電子マネー残高も確認しましょう。":"今週の予算は順調です。";

  const unpaid=state.cards.filter(c=>!c.paid).slice(0,3);
  let running=b.bank.amount;
  homeDebitList.innerHTML=unpaid.length?unpaid.map(c=>{
    running-=c.amount;
    return `<div class="debit-row"><div><b>${c.name}</b><div class="small">${c.date.slice(5).replace("-","/")} 引落予定</div></div><div class="num"><b>${yen(c.amount)}</b><div class="small">後 ${yen(running)}</div></div></div>`
  }).join(""):`<div class="empty">引き落とし予定はすべて反映済みです。</div>`;

  const alerts=[];
  if(unpaid.length)alerts.push(["💳",`${unpaid[0].name}の引き落とし予定`,`請求額 ${yen(unpaid[0].amount)}`]);
  if(w.remain<5000)alerts.push(["✉️","今週予算が少なくなっています",`残り ${yen(w.remain)}`]);
  if(b.paypay.amount<1000)alerts.push(["📱","PayPay残高が少なめです",`残り ${yen(b.paypay.amount)}`]);
  if(!alerts.length)alerts.push(["🌿","大きな注意事項はありません","このままのペースで大丈夫です"]);
  homeAlerts.innerHTML=alerts.map(a=>`<div class="alert-item"><div class="alert-icon">${a[0]}</div><div><b>${a[1]}</b><div class="small">${a[2]}</div></div></div>`).join("");

  const loggedToday=state.tx.some(t=>t.date===todayText);
  if(loggedToday){mission2.checked=true;saveMissions()}

  const expected={};
  Object.keys(b).forEach(k=>{
    const rows=state.tx.filter(t=>t.account===k);
    if(rows.length)expected[k]=rows[rows.length-1].balance;
  });
  const mismatches=Object.keys(expected).filter(k=>Number(expected[k])!==Number(b[k].amount));
  consistencyBox.className=mismatches.length?"consistency-warn":"consistency-ok";
  consistencyBox.textContent=mismatches.length
    ?`⚠ ${mismatches.map(k=>b[k].name).join("・")}の残高と通帳最終残高に差があります。`
    :"✓ 残高と通帳の整合性に問題はありません。";

  const topMethod=recommendedMethod.textContent;
  aiDailyComment.textContent=`今日は${topMethod}を優先すると、財布の現金を維持しやすいです。今週予算は${yen(w.remain)}残っています。`;
}



const purchaseGroups=[
  {name:"スーパー",icon:"🛒",stores:["イオン","コープ","トライアル","業務スーパー","西友"]},
  {name:"コンビニ",icon:"🏪",stores:["ローソン","セブン-イレブン","ファミリーマート","ミニストップ"]},
  {name:"ドラッグストア",icon:"💊",stores:["ツルハ","サツドラ","ウエルシア","マツモトキヨシ"]},
  {name:"外食",icon:"🍽️",stores:["レストラン","カフェ","ファストフード","テイクアウト"]},
  {name:"自販機",icon:"🥤",stores:["自販機"]},
  {name:"交通",icon:"🚃",stores:["バス","電車","タクシー","駐車場"]},
  {name:"子ども",icon:"📚",stores:["学校","公文","習い事","教材"]},
  {name:"その他",icon:"✏️",stores:[]}
];
const purchasePayments=[
  {label:"財布",key:"wallet",icon:"👛"},
  {label:"PayPay",key:"paypay",icon:"📱"},
  {label:"Suica",key:"suica",icon:"🚃"},
  {label:"イオンPay",key:"aeon",icon:"🩵"},
  {label:"楽天Pay",key:"rakuten",icon:"🟥"},
  {label:"QUO Pay",key:"quo",icon:"🎫"},
  {label:"銀行",key:"bank",icon:"🏦"},
  {label:"その他",key:"other",icon:"✏️"}
];
const purchaseExpenseCategories=["食費","日用品","交通","子ども","医療","娯楽","特別費","その他"];
let purchaseDraft={step:0,group:"",groupIcon:"",store:"",payment:"",account:"",paymentIcon:"",amount:"",category:""};

function purchaseHistory(){
  try{return JSON.parse(localStorage.getItem("familyOSPurchaseLearning")||'{"recent":[],"payments":{}}')}catch(e){return {recent:[],payments:{}}}
}
function savePurchaseHistory(h){localStorage.setItem("familyOSPurchaseLearning",JSON.stringify(h))}
function openPurchaseEntry(){
  purchaseEntry.classList.add("show");
  document.body.style.overflow="hidden";
  restartPurchaseEntry();
}
function closePurchaseEntry(){
  purchaseEntry.classList.remove("show");
  document.body.style.overflow="";
}
function restartPurchaseEntry(){
  purchaseDraft={step:0,group:"",groupIcon:"",store:"",payment:"",account:"",paymentIcon:"",amount:"",category:""};
  purchaseMemo2.value="";
  showPurchaseStep(0);
  renderPurchaseStart();
}
function showPurchaseStep(n){
  purchaseDraft.step=n;
  document.querySelectorAll(".entry-step").forEach((e,i)=>e.classList.toggle("active",i===n));
  purchaseDots.innerHTML=Array.from({length:6},(_,i)=>`<span class="step-dot ${i===Math.min(n,5)?"active":""}"></span>`).join("");
  purchaseBackBtn.style.visibility=n===0||n===6?"hidden":"visible";
  purchaseEntry.scrollTo(0,0);
}
function purchaseBack(){if(purchaseDraft.step>0&&purchaseDraft.step<6)showPurchaseStep(purchaseDraft.step-1)}
function renderPurchaseStart(){
  const h=purchaseHistory();
  const recent=h.recent||[];
  recentStoreChips.innerHTML=recent.length?recent.slice(0,6).map(r=>`<button class="recent-chip" onclick="selectRecentStore('${escJs(r.group)}','${escJs(r.icon)}','${escJs(r.store)}')">${r.icon} ${r.store}</button>`).join(""):`<span class="small">入力すると、ここに最近のお店が並びます。</span>`;
  purchaseCategoryGrid.innerHTML=purchaseGroups.map(g=>`<button class="tap-card" onclick="selectPurchaseGroup('${escJs(g.name)}','${g.icon}')"><span class="tap-icon">${g.icon}</span><strong>${g.name}</strong><small>タップして選択</small></button>`).join("");
}
function escJs(s){return String(s).replace(/\\/g,"\\\\").replace(/'/g,"\\'")}
function selectRecentStore(group,icon,store){
  purchaseDraft.group=group;purchaseDraft.groupIcon=icon;purchaseDraft.store=store;
  renderPurchasePayments();showPurchaseStep(2);
}
function selectPurchaseGroup(name,icon){
  purchaseDraft.group=name;purchaseDraft.groupIcon=icon;
  const g=purchaseGroups.find(x=>x.name===name);
  storeStepTitle.textContent=`${icon} ${name}のお店`;
  customStoreArea.style.display="none";
  const stores=[...(g?.stores||[])];
  purchaseStoreGrid.innerHTML=stores.map(s=>`<button class="tap-card" onclick="selectPurchaseStore('${escJs(s)}')"><span class="tap-icon">${icon}</span><strong>${s}</strong><small>選択して次へ</small></button>`).join("")+
    `<button class="tap-card wide" onclick="showCustomPurchaseStore()"><span class="tap-icon">✏️</span><strong>その他のお店</strong><small>店名を入力できます</small></button>`;
  showPurchaseStep(1);
}
function showCustomPurchaseStore(){customStoreArea.style.display="grid";purchaseCustomStore.focus()}
function selectPurchaseStoreCustom(){
  const v=purchaseCustomStore.value.trim();if(!v)return alert("お店の名前を入力してください");
  selectPurchaseStore(v);
}
function selectPurchaseStore(store){
  purchaseDraft.store=store;renderPurchasePayments();showPurchaseStep(2);
}
function renderPurchasePayments(){
  const h=purchaseHistory(), rec=h.payments?.[purchaseDraft.store];
  customPaymentArea2.style.display="none";
  paymentHint.textContent=rec?`${purchaseDraft.store}で前回使った「${rec.label}」を最初に表示しています。`:"支払い方法をタップしてください。";
  const sorted=[...purchasePayments].sort((a,b)=>(a.key===rec?.key?-1:0)-(b.key===rec?.key?-1:0));
  purchasePaymentGrid.innerHTML=sorted.map(p=>`<button class="tap-card ${p.key===rec?.key?"recommended":""}" onclick="${p.key==="other"?"showCustomPurchasePayment()":`selectPurchasePayment('${escJs(p.label)}','${p.key}','${p.icon}')`}"><span class="tap-icon">${p.icon}</span><strong>${p.key===rec?.key?"⭐ ":""}${p.label}</strong><small>${p.key===rec?.key?"前回と同じ":"タップして選択"}</small></button>`).join("");
}
function showCustomPurchasePayment(){customPaymentArea2.style.display="grid";purchaseCustomPayment.focus()}
function selectPurchasePaymentCustom(){
  const label=purchaseCustomPayment.value.trim();if(!label)return alert("支払い方法を入力してください");
  selectPurchasePayment(label,purchaseCustomAccount.value,"✏️");
}
function selectPurchasePayment(label,key,icon){
  purchaseDraft.payment=label;purchaseDraft.account=key;purchaseDraft.paymentIcon=icon;
  purchaseDraft.amount="";
  updatePurchaseAmount();
  showPurchaseStep(3);
}
function purchaseNum(v){
  if(v==="clear")purchaseDraft.amount="";
  else if(v==="back")purchaseDraft.amount=purchaseDraft.amount.slice(0,-1);
  else if(purchaseDraft.amount.length<8)purchaseDraft.amount=(purchaseDraft.amount==="0"?"":purchaseDraft.amount)+v;
  updatePurchaseAmount();
}
function updatePurchaseAmount(){purchaseAmountDisplay.innerHTML=`${Number(purchaseDraft.amount||0).toLocaleString("ja-JP")}<span>円</span>`}
function purchaseNextFromAmount(){
  if(Number(purchaseDraft.amount)<=0)return alert("金額を入力してください");
  purchaseExpenseCategoryGrid.innerHTML=purchaseExpenseCategories.map(c=>`<button class="tap-card" onclick="selectExpenseCategory('${c}')"><span class="tap-icon">${expenseCategoryIcon(c)}</span><strong>${c}</strong><small>タップして選択</small></button>`).join("");
  showPurchaseStep(4);
}
function expenseCategoryIcon(c){return {"食費":"🍚","日用品":"🧻","交通":"🚃","子ども":"🎒","医療":"🏥","娯楽":"🎈","特別費":"🎁","その他":"✏️"}[c]||"✏️"}
function selectExpenseCategory(c){
  purchaseDraft.category=c;
  purchaseConfirmSummary.innerHTML=`<b>${purchaseDraft.groupIcon} ${purchaseDraft.store}</b><br>${purchaseDraft.paymentIcon} ${purchaseDraft.payment}<br>${expenseCategoryIcon(c)} ${c}<br><b style="font-size:24px">${Number(purchaseDraft.amount).toLocaleString("ja-JP")}円</b>`;
  showPurchaseStep(5);
}
function savePurchaseEntry(){
  const d=purchaseDraft,amount=Number(d.amount),account=d.account;
  if(!state.balances[account])return alert("支払い元が見つかりません");
  if(state.balances[account].amount<amount)return alert(`${state.balances[account].name}の残高が不足しています`);
  snapshot();
  const now=new Date(),date=`${now.getMonth()+1}/${now.getDate()}`,createdAt=now.toISOString();
  state.balances[account].amount-=amount;
  state.tx.push({date,account,desc:d.store,category:d.category,memo:purchaseMemo2.value.trim(),in:0,out:amount,balance:state.balances[account].amount,createdAt});
  const wi=currentWeekIndex(date);
  if(wi>=0&&["食費","日用品","交通","子ども","その他"].includes(d.category))state.weeks[wi].remain-=amount;
  const h=purchaseHistory();
  h.recent=(h.recent||[]).filter(r=>r.store!==d.store);
  h.recent.unshift({group:d.group,icon:d.groupIcon,store:d.store});
  h.recent=h.recent.slice(0,12);
  h.payments=h.payments||{};
  h.payments[d.store]={label:d.payment,key:d.account,icon:d.paymentIcon};
  savePurchaseHistory(h);
  save();render();
  purchaseDoneText.textContent=`${d.store} ${amount.toLocaleString("ja-JP")}円を記録しました。`;
  showPurchaseStep(6);
}

const favoriteShopOptions=[
  ["スーパー","🛒"],["コンビニ","🏪"],["コープ","🥬"],["ドラッグストア","💊"],
  ["自販機","🥤"],["外食","🍽️"],["学校・習い事","📚"],["その他","✏️"]
];
const favoritePaymentOptions=[
  ["財布","wallet","👛"],["PayPay","paypay","📱"],["Suica","suica","🚃"],
  ["イオンPay","aeon","🩵"],["楽天Pay","rakuten","🟥"],["QUO Pay","quo","🎫"],
  ["銀行","bank","🏦"],["その他","other","✏️"]
];
let favoriteDraft={shop:"",shopIcon:"",payment:"",paymentKey:"",paymentIcon:""};
let selectedFavoriteIndex=null;

function getFavorites(){
  try{return JSON.parse(localStorage.getItem("familyOSFavorites")||"[]")}catch(e){return[]}
}
function setFavorites(list){localStorage.setItem("familyOSFavorites",JSON.stringify(list))}
function openFavoriteEntry(){
  favoriteModal.classList.add("show");
  renderFavoriteCards();
  backToFavorites();
}
function closeFavoriteEntry(){favoriteModal.classList.remove("show")}
function backToFavorites(){
  favoriteListWrap.style.display="";
  favoriteSetupWrap.style.display="none";
  favoriteUseWrap.style.display="none";
  renderFavoriteCards();
}
function renderFavoriteCards(){
  const list=getFavorites();
  favoriteCards.innerHTML=list.length?list.map((f,i)=>`
    <div class="favorite-card" onclick="useFavorite(${i})">
      <div class="fav-title">${f.shopIcon||"⭐"} ${f.shop}</div>
      <div class="fav-meta">${f.paymentIcon||""} ${f.paymentLabel}${f.amount?`・${yen(f.amount)}`:"・金額入力"}</div>
      <div class="fav-meta">${f.category||"その他"}${f.memo?`｜${f.memo}`:""}</div>
    </div>`).join(""):`<div class="empty">まだお気に入りがありません。</div>`;
}
function startFavoriteSetup(){
  favoriteListWrap.style.display="none";
  favoriteSetupWrap.style.display="";
  favoriteUseWrap.style.display="none";
  favoriteDraft={shop:"",shopIcon:"",payment:"",paymentKey:"",paymentIcon:""};
  customShopName.value="";customPaymentName.value="";favAmount.value="";favMemo.value="";
  renderShopChoices();renderPaymentChoices();updateFavoriteSummary();
}
function renderShopChoices(){
  shopChoices.innerHTML=favoriteShopOptions.map(([name,icon])=>`
    <button class="choice ${favoriteDraft.shop===name?"selected":""}" onclick="selectShop('${name}','${icon}')">
      <b>${icon} ${name}</b><span class="small">タップして選択</span>
    </button>`).join("");
}
function renderPaymentChoices(){
  paymentChoices.innerHTML=favoritePaymentOptions.map(([label,key,icon])=>`
    <button class="choice ${favoriteDraft.payment===label?"selected":""}" onclick="selectPayment('${label}','${key}','${icon}')">
      <b>${icon} ${label}</b><span class="small">タップして選択</span>
    </button>`).join("");
}
function selectShop(name,icon){
  favoriteDraft.shop=name;favoriteDraft.shopIcon=icon;
  otherShopFields.classList.toggle("show",name==="その他");
  renderShopChoices();updateFavoriteSummary();
}
function selectPayment(label,key,icon){
  favoriteDraft.payment=label;favoriteDraft.paymentKey=key;favoriteDraft.paymentIcon=icon;
  otherPaymentFields.classList.toggle("show",key==="other");
  renderPaymentChoices();updateFavoriteSummary();
}
function updateFavoriteSummary(){
  const shop=favoriteDraft.shop==="その他"?(customShopName.value||"その他のお店"):favoriteDraft.shop;
  const pay=favoriteDraft.paymentKey==="other"?(customPaymentName.value||"その他の支払い"):favoriteDraft.payment;
  favoriteSummary.textContent=shop&&pay?`${favoriteDraft.shopIcon} ${shop} × ${favoriteDraft.paymentIcon} ${pay}`:"お店と支払い方法を選んでください。";
}
function saveFavorite(){
  let shop=favoriteDraft.shop==="その他"?customShopName.value.trim():favoriteDraft.shop;
  let paymentLabel=favoriteDraft.paymentKey==="other"?customPaymentName.value.trim():favoriteDraft.payment;
  if(!shop)return alert("お店を選択または入力してください");
  if(!paymentLabel)return alert("支払い方法を選択または入力してください");
  const paymentKey=favoriteDraft.paymentKey==="other"?"wallet":favoriteDraft.paymentKey;
  const list=getFavorites();
  list.push({
    shop,shopIcon:favoriteDraft.shopIcon||"⭐",
    paymentLabel,paymentKey,paymentIcon:favoriteDraft.paymentIcon||"💳",
    amount:Number(favAmount.value||0),category:favCategory.value,memo:favMemo.value.trim()
  });
  setFavorites(list);backToFavorites();
}
function useFavorite(i){
  const f=getFavorites()[i];if(!f)return;
  selectedFavoriteIndex=i;
  favoriteListWrap.style.display="none";
  favoriteSetupWrap.style.display="none";
  favoriteUseWrap.style.display="";
  favoriteUseSummary.innerHTML=`<b>${f.shopIcon} ${f.shop}</b><div class="small">${f.paymentIcon} ${f.paymentLabel}｜${f.category}</div>`;
  favoriteUseAmount.value=f.amount||"";
  favoriteUseMemo.value=f.memo||"";
  favoriteUseAmount.focus();
}
function applyFavoriteEntry(){
  const f=getFavorites()[selectedFavoriteIndex];if(!f)return;
  const amount=Number(favoriteUseAmount.value||0);
  if(amount<=0)return alert("金額を入力してください");
  const account=f.paymentKey in state.balances?f.paymentKey:"wallet";
  if(state.balances[account].amount<amount)return alert(`${state.balances[account].name}の残高が不足しています`);
  snapshot();
  const d=new Date();
  const date=`${d.getMonth()+1}/${d.getDate()}`;
  state.balances[account].amount-=amount;
  state.tx.push({
    date,account,desc:f.shop,category:f.category||"その他",
    memo:favoriteUseMemo.value.trim(),in:0,out:amount,
    balance:state.balances[account].amount,createdAt:new Date().toISOString()
  });
  const wi=currentWeekIndex(date);
  if(wi>=0&&state.weeks[wi])state.weeks[wi].remain-=amount;
  save();render();closeFavoriteEntry();
}
document.addEventListener("input",e=>{
  if(e.target===customShopName||e.target===customPaymentName)updateFavoriteSummary();
});

function render(){const b=state.balances;const em=b.paypay.amount+b.suica.amount+b.quo.amount+b.aeon.amount+b.rakuten.amount;usableTotal.textContent=yen(b.envelopes.amount+em);envTotalTop.textContent=yen(b.envelopes.amount);emoneyTotalTop.textContent=yen(em);const order=["bank","wallet","envelopes","paypay","suica","quo","aeon","rakuten","reimbursement"];balanceCards.innerHTML=order.map(k=>`<div class="money-card"><div class="name">${b[k].icon} ${b[k].name}</div><div class="amount">${yen(b[k].amount)}</div><button onclick="openBook('${k}')">通帳を見る</button></div>`).join("");weekList.innerHTML=state.weeks.map(w=>`<div class="week-row"><b>${w.name}</b><div class="bar"><span style="width:${Math.max(0,w.remain/w.budget*100)}%"></span></div><div class="num">${yen(w.remain)}</div></div>`).join("");txAccount.innerHTML=order.map(k=>`<option value="${k}">${b[k].name}</option>`).join("");txToAccount.innerHTML=order.map(k=>`<option value="${k}">${b[k].name}</option>`).join("");txWeek.innerHTML=`<option value="auto">週予算：自動</option><option value="none">週予算から引かない</option>`+state.weeks.map((w,i)=>`<option value="${i}">${w.name}</option>`).join("");renderBooks();renderCards();updateRecommendation();updateTodayFlow();updateHealth();
  const now=new Date();todayLabel.textContent=now.toLocaleDateString("ja-JP",{year:"numeric",month:"long",day:"numeric",weekday:"short"});
  const accountOptions=`<option value="all">すべての口座</option>`+order.map(k=>`<option value="${k}">${b[k].name}</option>`).join("");
  if(historyAccount.options.length!==order.length+1)historyAccount.innerHTML=accountOptions;
  const categories=[...new Set(state.tx.map(t=>t.category||"—"))].sort();
  historyCategory.innerHTML=`<option value="all">すべての区分</option>`+categories.map(c=>`<option>${c}</option>`).join("");
  renderHistory();renderReport();renderHome();loadMissions();
  envelopeTable.innerHTML=`<table><thead><tr><th>封筒</th><th>予算</th><th>残り</th></tr></thead><tbody>${state.weeks.map(w=>`<tr><td>${w.name}</td><td class="num">${yen(w.budget)}</td><td class="num">${yen(w.remain)}</td></tr>`).join("")}<tr><td>立替封筒（コープ）</td><td class="num">—</td><td class="num">${yen(b.reimbursement.amount)}</td></tr></tbody></table>`}
function renderBooks(){const keys=["bank","wallet","envelopes","paypay","suica","quo","aeon","rakuten","reimbursement"];passbookWrap.innerHTML=keys.map(k=>{const rows=state.tx.filter(t=>t.account===k);return `<div class="panel"><h2>${state.balances[k].icon} ${state.balances[k].name}通帳　<span class="small">現在残高 ${yen(state.balances[k].amount)}</span></h2><table><thead><tr><th>日付</th><th>摘要</th><th>区分</th><th>入金</th><th>出金</th><th>残高</th></tr></thead><tbody>${rows.map(t=>`<tr><td>${t.date}</td><td>${t.desc}${t.memo?`<div class="small">${t.memo}</div>`:""}</td><td>${t.category||"—"}</td><td class="num positive">${t.in?yen(t.in):""}</td><td class="num negative">${t.out?yen(t.out):""}</td><td class="num">${yen(t.balance)}</td></tr>`).join("")}</tbody></table></div>`}).join("")}
function toggleTransfer(){const moving=txType.value==="transfer"||txType.value==="settlement";txToAccount.style.display=moving?"block":"none"}
function addTransaction(){const account=txAccount.value,toAccount=txToAccount.value,amount=Number(txAmount.value),type=txType.value,desc=txDesc.value.trim(),category=txCategory.value,memo=txMemo.value.trim();if(!amount||amount<=0||!desc)return alert("摘要と正しい金額を入力してください");if((type==="transfer"||type==="settlement")&&account===toAccount)return alert("移動元と移動先は別にしてください");if(type!=="in"&&state.balances[account].amount<amount)return alert("残高が不足しています");const date=txDate.value.slice(5).replace("-","/"),createdAt=new Date().toISOString();if(type==="in"){state.balances[account].amount+=amount;state.tx.push({date,account,desc,category,memo,in:amount,out:0,balance:state.balances[account].amount,createdAt})}else if(type==="out"){state.balances[account].amount-=amount;state.tx.push({date,account,desc,category,memo,in:0,out:amount,balance:state.balances[account].amount,createdAt});let wi=txWeek.value==="auto"?currentWeekIndex(date):Number(txWeek.value);if(txWeek.value!=="none"&&wi>=0&&["食費","日用品","交通","子ども","その他"].includes(category))state.weeks[wi].remain-=amount}else{const label=type==="settlement"?"立替・精算":"資金移動";state.balances[account].amount-=amount;state.tx.push({date,account,desc:`${desc}（${state.balances[toAccount].name}へ）`,category:label,memo,in:0,out:amount,balance:state.balances[account].amount,createdAt});state.balances[toAccount].amount+=amount;state.tx.push({date,account:toAccount,desc:`${desc}（${state.balances[account].name}から）`,category:label,memo,in:amount,out:0,balance:state.balances[toAccount].amount,createdAt})}save();txDesc.value="";txAmount.value="";txMemo.value="";render();alert("通帳へ記録しました")}
function exportData(){const blob=new Blob([JSON.stringify(state,null,2)],{type:"application/json"});const a=document.createElement("a");a.href=URL.createObjectURL(blob);a.download="Family_OS_backup_"+new Date().toISOString().slice(0,10)+".json";a.click();URL.revokeObjectURL(a.href)}
function importData(event){const file=event.target.files[0];if(!file)return;const reader=new FileReader();reader.onload=()=>{try{const imported=JSON.parse(reader.result);if(!imported.balances||!imported.tx)throw new Error();state=imported;save();render();alert("バックアップを復元しました")}catch(e){alert("Family OSのバックアップを読み込めませんでした")}event.target.value=""};reader.readAsText(file)}
function openBook(k){document.querySelectorAll("nav button").forEach(x=>x.classList.remove("active"));document.querySelector('[data-page="passbooks"]').classList.add("active");document.querySelectorAll(".page").forEach(x=>x.classList.remove("active"));passbooks.classList.add("active")}
document.querySelectorAll("nav button").forEach(btn=>btn.addEventListener("click",()=>{document.querySelectorAll("nav button").forEach(x=>x.classList.remove("active"));btn.classList.add("active");document.querySelectorAll(".page").forEach(x=>x.classList.remove("active"));document.getElementById(btn.dataset.page).classList.add("active")}));normalizeState();render();
</script>
<button class="fab" onclick="openPurchaseEntry()" aria-label="購入を入力">＋</button>

<div class="modal-backdrop" id="favoriteModal" onclick="if(event.target===this)closeFavoriteEntry()">
  <div class="sheet">
    <div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
      <div>
        <div class="small">ワンタップ家計入力</div>
        <h2>お気に入りから入力</h2>
      </div>
      <button class="mini" onclick="closeFavoriteEntry()">閉じる</button>
    </div>

    <div id="favoriteListWrap">
      <div class="step-label">登録済みのお気に入り</div>
      <div class="favorite-list" id="favoriteCards"></div>
      <button class="btn" style="margin-top:12px" onclick="startFavoriteSetup()">＋ 新しいお気に入りを登録</button>
    </div>

    <div id="favoriteSetupWrap" style="display:none">
      <div class="step-label">1. お店を選択</div>
      <div class="choice-grid" id="shopChoices"></div>
      <div class="other-fields" id="otherShopFields">
        <input id="customShopName" placeholder="お店の名前を入力">
      </div>

      <div class="step-label">2. 支払い方法を選択</div>
      <div class="choice-grid" id="paymentChoices"></div>
      <div class="other-fields" id="otherPaymentFields">
        <input id="customPaymentName" placeholder="支払い方法の名前を入力">
      </div>

      <div class="step-label">3. よく使う内容</div>
      <div class="form-grid">
        <input id="favAmount" type="number" min="0" placeholder="金額（毎回変わるなら空欄）">
        <select id="favCategory">
          <option>食費</option><option>日用品</option><option>交通</option><option>医療</option>
          <option>子ども</option><option>娯楽</option><option>特別費</option><option>その他</option>
        </select>
        <input id="favMemo" placeholder="メモ（例：家族分の飲み物）">
      </div>

      <div class="favorite-summary" id="favoriteSummary">お店と支払い方法を選んでください。</div>

      <div class="modal-actions">
        <button class="mini" onclick="backToFavorites()">戻る</button>
        <button class="btn" onclick="saveFavorite()">お気に入り登録</button>
      </div>
    </div>

    <div id="favoriteUseWrap" style="display:none">
      <div class="favorite-summary" id="favoriteUseSummary"></div>
      <div class="step-label">今回の金額</div>
      <input id="favoriteUseAmount" type="number" min="0" placeholder="金額を入力">
      <div class="step-label">メモ</div>
      <input id="favoriteUseMemo" placeholder="今回だけのメモ">
      <div class="modal-actions">
        <button class="mini" onclick="backToFavorites()">戻る</button>
        <button class="btn" onclick="applyFavoriteEntry()">この内容で記録</button>
      </div>
    </div>
  </div>
</div>


<div class="mobile-entry" id="purchaseEntry">
  <div class="mobile-entry-head">
    <button class="entry-back" id="purchaseBackBtn" onclick="purchaseBack()">← 戻る</button>
    <h2>かんたん入力</h2>
    <button class="entry-back" onclick="closePurchaseEntry()">閉じる</button>
  </div>
  <div class="step-dots" id="purchaseDots"></div>
  <div class="mobile-entry-body">

    <section class="entry-step active" id="purchaseStep0">
      <div class="entry-title">どこで使いましたか？</div>
      <div class="entry-help">文字入力せず、タップだけで進めます。</div>
      <div class="quick-start-box">
        <b>最近使ったお店</b>
        <div class="recent-strip" id="recentStoreChips" style="margin-top:10px"></div>
      </div>
      <div class="tap-grid" id="purchaseCategoryGrid"></div>
    </section>

    <section class="entry-step" id="purchaseStep1">
      <div class="entry-title" id="storeStepTitle">お店を選択</div>
      <div class="entry-help">よく使うお店は次回から上に表示されます。</div>
      <div class="tap-grid" id="purchaseStoreGrid"></div>
      <div class="custom-entry" id="customStoreArea" style="display:none">
        <input id="purchaseCustomStore" placeholder="お店の名前を入力">
        <button class="entry-save" onclick="selectPurchaseStoreCustom()">この店名で進む</button>
      </div>
    </section>

    <section class="entry-step" id="purchaseStep2">
      <div class="entry-title">支払い方法は？</div>
      <div class="entry-help" id="paymentHint">前回使った方法を一番上に表示します。</div>
      <div class="tap-grid" id="purchasePaymentGrid"></div>
      <div class="custom-entry" id="customPaymentArea2" style="display:none">
        <input id="purchaseCustomPayment" placeholder="支払い方法を入力">
        <select id="purchaseCustomAccount">
          <option value="wallet">財布残高から引く</option>
          <option value="bank">銀行残高から引く</option>
          <option value="paypay">PayPay残高から引く</option>
        </select>
        <button class="entry-save" onclick="selectPurchasePaymentCustom()">この方法で進む</button>
      </div>
    </section>

    <section class="entry-step" id="purchaseStep3">
      <div class="entry-title">いくら使いましたか？</div>
      <div class="entry-help">数字をタップしてください。</div>
      <div class="number-wrap">
        <div class="number-display" id="purchaseAmountDisplay">0<span>円</span></div>
        <div class="num-pad">
          <button class="num-key" onclick="purchaseNum('1')">1</button>
          <button class="num-key" onclick="purchaseNum('2')">2</button>
          <button class="num-key" onclick="purchaseNum('3')">3</button>
          <button class="num-key" onclick="purchaseNum('4')">4</button>
          <button class="num-key" onclick="purchaseNum('5')">5</button>
          <button class="num-key" onclick="purchaseNum('6')">6</button>
          <button class="num-key" onclick="purchaseNum('7')">7</button>
          <button class="num-key" onclick="purchaseNum('8')">8</button>
          <button class="num-key" onclick="purchaseNum('9')">9</button>
          <button class="num-key action" onclick="purchaseNum('clear')">クリア</button>
          <button class="num-key" onclick="purchaseNum('0')">0</button>
          <button class="num-key action" onclick="purchaseNum('back')">⌫</button>
        </div>
      </div>
      <button class="entry-save" style="margin-top:14px" onclick="purchaseNextFromAmount()">次へ</button>
    </section>

    <section class="entry-step" id="purchaseStep4">
      <div class="entry-title">何のお金ですか？</div>
      <div class="entry-help">よく使うものを大きく表示しています。</div>
      <div class="tap-grid" id="purchaseExpenseCategoryGrid"></div>
    </section>

    <section class="entry-step" id="purchaseStep5">
      <div class="entry-title">この内容で記録します</div>
      <div class="entry-summary" id="purchaseConfirmSummary"></div>
      <input id="purchaseMemo2" placeholder="メモ（省略できます）" style="margin-bottom:12px">
      <button class="entry-save" onclick="savePurchaseEntry()">✓ 記録する</button>
    </section>

    <section class="entry-step" id="purchaseStep6">
      <div style="text-align:center;padding:34px 10px">
        <div style="font-size:58px">✓</div>
        <div class="entry-title">記録できました！</div>
        <div class="entry-help" id="purchaseDoneText"></div>
        <button class="entry-save" onclick="closePurchaseEntry()">HOMEに戻る</button>
        <button class="entry-back" style="margin-top:12px;width:100%" onclick="restartPurchaseEntry()">続けて入力</button>
      </div>
    </section>
  </div>
</div>


<script>
if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker.register("./service-worker.js").catch(() => {});
  });
}
</script>

</body></html>
