無題

Pasted Oct 13, 2026, 12:08 PM1 file4,455 lines

Scan to open on your phone

Point a camera at the QR code - it opens this exact paste, no app needed.

https://codepastes.com/zzgsxbxepyv5y9bc
無題
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>記録</title>

<style>
:root{
  --main:#4f86c6;
  --light:#eaf3ff;
  --bg:#f4f6f8;
  --text:#333;
  --panelText:#333;
  --inputBg:rgba(255,255,255,.96);
  --inputText:#333;
  --border:#ccc;
  --titleFont:"Yu Gothic","Meiryo",sans-serif;
  --dateFont:"Yu Gothic","Meiryo",sans-serif;
  --bodyFont:"Yu Gothic","Meiryo",sans-serif;
  --commentFont:"Yu Gothic","Meiryo",sans-serif;
  --listFont:"Yu Gothic","Meiryo",sans-serif;
  --settingFont:"Yu Gothic","Meiryo",sans-serif;
  --titleSize:32px;
  --dateSize:18px;
  --bodySize:18px;
  --commentSize:16px;
  --listSize:18px;
  --settingSize:16px;
  --photoColumns:3;
  --panelBg:rgba(255,255,255,1);
  --photoBg:rgba(255,255,255,1);
  --darkBorder:#444;
}

*{box-sizing:border-box}

body{
  margin:0;
  color:var(--text);
  background:var(--bg);
  background-size:cover;
  background-position:center;
  background-attachment:fixed;
  font-family:var(--bodyFont);
  transition:.2s
}

header{
  height:64px;
  display:flex;
  align-items:center;
  padding:0 22px;
  background:var(--main);
  color:white
}

header h1{
  margin:0;
  font-family:var(--titleFont);
  font-size:var(--appTitleSize);
  letter-spacing:.04em
}

.layout{
  min-height:calc(100vh - 64px);
  display:flex
}

.sidebar{
  width:205px;
  flex-shrink:0;
  background:var(--panelBg);
  border-right:1px solid #ddd;
  padding:10px;
  display:flex;
  flex-direction:column
}

.side-btn{
  width:100%;
  border:0;
  border-radius:8px;
  padding:6px 10px;
  margin-bottom:4px;
  min-height:calc(var(--titleSize) * 1.8);
  box-sizing:border-box;
  background:transparent;
  text-align:left;
  cursor:pointer;
  font-family:var(--titleFont);
  font-size:var(--titleSize);
  line-height:1.4;
  color:var(--panelText)
}

.side-btn:hover{
  background:var(--light)
}

.side-btn.active{
  background:var(--main);
  color:white;
  font-weight:bold
}

.sidebar-space{
  flex:1
}

main{
  flex:1;
  min-width:0;
  padding:24px
}

.panel,
.detail,
.settings-section{
  background:var(--panelBg);
  border-radius:12px;
  padding:24px;
  box-shadow:0 2px 9px #0000000c;
  color:var(--panelText)
}

input,
textarea{
  width:100%;
  border:1px solid var(--border);
  border-radius:8px;
  padding:10px;
  background:var(--inputBg);
  color:var(--inputText)
}

input{
  margin-bottom:12px;
  font-family:var(--bodyFont);
  font-size:var(--bodySize)
}

textarea{
  min-height:calc(var(--bodySize) * 13);
  resize:vertical;
  font-family:var(--bodyFont);
  font-size:var(--bodySize);
  line-height:1.8
}

.editor-heading{
  margin:0 0 18px;
  padding:12px 0;
  border-bottom:2px solid var(--main)
}

.editor-heading-title{
  display:block;
  font-family:var(--titleFont);
  font-size:24px;
  font-weight:bold;
  line-height:1.4;
  color:var(--main)
}

.title-input{
  font-family:var(--editFont);
  font-size:var(--editSize);
  font-weight:bold;
  border:1px solid var(--border);
  border-radius:8px;
  padding:11px 13px;
  background:var(--inputBg);
  margin-bottom:12px
}

.title-input:focus{
  outline:none;
  border-color:var(--main);
  box-shadow:0 0 0 3px rgba(79,134,198,.12)
}

.date-row{
  display:flex;
  align-items:center;
  margin-bottom:12px
}

.date-input-wrap{
  display:flex;
  align-items:center;
  width:max-content;
  border:1px solid var(--border);
  border-radius:8px;
  padding:0 8px 0 10px;
  background:var(--inputBg);
  color:var(--inputText)
}

.date-input-wrap input{
  width:auto;
  min-width:0;
  flex:0 0 auto;
  margin:0;
  padding:10px 4px 10px 0;
  border:0;
  border-radius:0;
  background:transparent;
  font-family:var(--editFont);
  font-size:var(--editSize);
  font-weight:bold;
  color:var(--inputText)
}

.date-input-wrap input:focus{
  outline:none
}

.weekday-display{
  white-space:nowrap;
  flex-shrink:0;
  font-family:var(--editFont);
  font-size:var(--editSize);
  font-weight:bold;
  margin-left:4px
}

/* 写真欄 */
.photo-count-editor{
  margin:15px 0;
  padding:12px;
  border:1px solid #ddd;
  border-radius:9px;
  background:rgba(248,250,252,.8)
}

.photo-count-editor-title{
  margin:0 0 9px;
  font-family:var(--editFont);
  font-size:var(--editSize);
  font-weight:bold;
  color:var(--main)
}

.photo-count-editor-buttons{
  display:flex;
  gap:5px;
  flex-wrap:wrap
}

.photo-count-editor-button{
  border:1px solid #ccc;
  border-radius:6px;
  background:var(--panelBg);
  color:var(--panelText);
  padding:8px 12px;
  cursor:pointer;
  font-family:var(--editFont);
  font-size:var(--editSize)
}

.photo-count-editor-button.selected{
  background:var(--main);
  color:white
}

.action{
  border:0;
  border-radius:7px;
  padding:10px 16px;
  margin:5px 4px 5px 0;
  cursor:pointer;
  font-family:var(--settingFont);
  font-size:var(--settingSize)
}

.edit-action{
  font-family:var(--editFont);
  font-size:var(--editSize)
}

.action:disabled{
  opacity:.55;
  cursor:not-allowed
}

.save{
  background:var(--main);
  color:white
}

.cancel{
  background:#ddd;
  color:#333
}

.delete{
  background:#c85656;
  color:white
}

.year-buttons{
  display:flex;
  gap:7px;
  flex-wrap:wrap;
  margin-bottom:15px
}

.year-buttons button{
  border:1px solid #ccc;
  border-radius:7px;
  background:var(--panelBg);
  color:var(--panelText);
  padding:8px 15px;
  cursor:pointer;
  font-family:var(--dateFont);
  font-size:var(--dateSize)
}

.year-buttons button.active{
  background:var(--main);
  color:white
}

.trip{
  display:flex;
  align-items:center;
  padding:15px 18px;
  border-bottom:1px solid #eee;
  cursor:pointer
}

.trip:hover{
  background:var(--light)
}

.trip-date{
  width:250px;
  flex-shrink:0;
  color:#777;
  font-family:var(--dateFont);
  font-size:var(--dateSize)
}

.trip-title{
  font-family:var(--dateFont);
  font-size:var(--dateSize);
  font-weight:bold
}

.detail-title{
  border-left:6px solid var(--main);
  border-radius:8px;
  background:var(--light);
  padding:15px 18px;
  margin-bottom:10px;
  font-family:var(--titleFont);
  font-size:var(--titleSize);
  font-weight:bold
}

.detail-date{
  display:inline-block;
  padding:7px 12px;
  margin-bottom:18px;
  border-radius:7px;
  background:var(--main);
  color:white;
  font-family:var(--dateFont);
  font-size:var(--dateSize);
  font-weight:bold
}

.detail-text{
  padding:18px;
  margin-bottom:20px;
  border:1px solid #e4e4e4;
  border-radius:9px;
  white-space:pre-wrap;
  overflow-wrap:anywhere;
  font-family:var(--bodyFont);
  font-size:var(--bodySize);
  line-height:1.9
}

.photo-grid{
  display:grid;
  grid-template-columns:repeat(var(--photoColumns),minmax(0,1fr));
  gap:12px;
  width:100%;
  border:2px solid #b8b8b8;
  border-radius:10px;
  padding:10px
}

.photo-item{
  min-width:0;
  padding:7px;
  border:2px solid #aaa;
  border-radius:9px;
  background:var(--photoBg);
  cursor:grab;
  user-select:none
}

.photo-item.dragging,
.edit-photo.dragging{
  opacity:.35
}

.photo-item.drag-over,
.edit-photo.drag-over{
  outline:3px solid var(--main)
}

.photo-frame{
  width:100%;
  overflow:hidden;
  border:2px solid #999;
  border-radius:7px;
  background:#eee
}

.photo-frame img{
  width:100%;
  height:auto;
  display:block;
  object-fit:contain;
  cursor:pointer
}

.photo-comment-box{
  margin-top:6px;
  padding:6px 8px;
  border:1px solid #aaa;
  border-radius:6px;
  background:rgba(255,255,255,.72)
}

.photo-comment{
  font-family:var(--commentFont);
  font-size:var(--commentSize);
  line-height:1.5;
  white-space:pre-wrap;
  overflow-wrap:anywhere
}

.edit-photo-grid{
  display:grid;
  grid-template-columns:repeat(var(--photoColumns),minmax(0,1fr));
  gap:12px;
  margin:15px 0
}

.edit-photo{
  min-width:0;
  padding:7px;
  border:2px solid #aaa;
  border-radius:9px;
  background:var(--photoBg);
  cursor:grab
}

.edit-photo-frame{
  overflow:hidden;
  border:2px solid #999;
  border-radius:7px;
  background:#eee
}

.edit-photo-frame img{
  width:100%;
  height:auto;
  display:block
}

.edit-comment-box{
  margin-top:6px;
  padding:5px;
  border:1px solid #aaa;
  border-radius:6px
}

.edit-photo textarea{
  min-height:70px;
  margin:0;
  font-family:var(--commentFont);
  font-size:var(--commentSize)
}

.drop-zone{
  border:2px dashed #aab9b2;
  border-radius:10px;
  padding:20px;
  text-align:center;
  color:var(--main);
  background:rgba(250,252,249,.8);
  cursor:pointer;
  line-height:1.7;
  font-family:var(--settingFont)
}

.drop-zone.dragover{
  background:var(--light)
}

.save-status,
.draft-status{
  margin:10px 0;
  padding:10px;
  border-radius:7px;
  display:none;
  font-family:var(--settingFont)
}

.save-status{
  background:#fff8d9;
  color:#775f00
}

.draft-status{
  background:#eef7ee;
  color:#397039
}

.settings-section{
  padding:18px;
  margin-bottom:15px;
  border:1px solid #ddd;
  border-radius:10px;
  background:var(--panelBg);
  font-family:var(--settingFont)
}

.settings-section input{
  font-family:var(--settingFont);
  font-size:var(--settingSize)
}

.settings-section h2{
  margin:0 0 14px;
  color:var(--main);
  font-family:var(--settingFont)
}

.color-list{
  display:flex;
  flex-wrap:wrap;
  gap:7px
}

.color-choice{
  width:44px;
  height:44px;
  border:3px solid transparent;
  border-radius:50%;
  cursor:grab;
  flex:0 0 44px
}

.color-choice.selected{
  border-color:#333;
  box-shadow:0 0 0 2px white inset
}

.color-choice.white-color{
  border-color:#333
}

.color-choice.white-color.selected{
  border-color:#000;
  box-shadow:0 0 0 2px white inset,0 0 0 3px #333
}

.size-setting{
  padding:10px 0;
  border-bottom:1px solid #eee
}

.size-setting-row{
  display:flex;
  align-items:center;
  gap:10px;
  flex-wrap:wrap
}

.size-setting-name{
  width:80px;
  font-weight:bold
}

.size-buttons{
  display:flex;
  gap:4px
}

.size-button{
  min-width:34px;
  min-height:32px;
  padding:4px 8px;
  border:1px solid #ccc;
  border-radius:6px;
  background:var(--panelBg);
  color:var(--panelText);
  cursor:pointer;
  font-family:var(--settingFont);
  font-size:var(--settingSize);
  line-height:1.2
}

.size-button.selected{
  background:var(--main);
  color:white
}

.font-select{
  display:flex;
  align-items:center;
  gap:5px
}

.font-choice{
  min-width:105px;
  height:34px;
  padding:4px 8px;
  border:1px solid #ccc;
  border-radius:6px;
  background:var(--panelBg);
  color:var(--panelText);
  cursor:pointer;
  text-align:left;
  font-size:var(--settingSize)
}

.font-choice.selected{
  border:2px solid var(--main);
  background:var(--light)
}

.font-choice-name{
  margin-right:7px;
  color:#555
}

.font-choice-sample{
  font-size:20px;
  font-weight:normal
}

.transparency-row{
  display:flex;
  gap:5px;
  flex-wrap:wrap
}

.transparency-button{
  border:1px solid #ccc;
  border-radius:6px;
  background:var(--panelBg);
  color:var(--panelText);
  padding:8px 12px;
  cursor:pointer
}

.transparency-button.selected{
  background:var(--main);
  color:white
}

.dark-mode-row{
  display:flex;
  gap:5px;
  flex-wrap:wrap
}

.dark-mode-button{
  border:1px solid #ccc;
  border-radius:6px;
  background:var(--panelBg);
  color:var(--panelText);
  padding:8px 18px;
  cursor:pointer;
  font-family:var(--settingFont)
}

.dark-mode-button.selected{
  background:var(--main);
  color:white
}

.background-preview{
  width:100%;
  max-height:280px;
  object-fit:cover;
  border-radius:9px;
  margin-top:10px
}

.photo-window{
  position:fixed;
  inset:0;
  z-index:9999;
  background:#000e;
  display:flex;
  justify-content:center;
  align-items:center
}

/* =========================
   起動・フォルダ選択画面
   ヘッダー/サイドバー一時非表示
========================= */
body.permission-mode header,
body.permission-mode .sidebar{
  display:none;
}

body.permission-mode .layout{
  min-height:100vh;
}

body.permission-mode main{
  width:100%;
  min-height:100vh;
  padding:0;
}

/* =========================
   メモウィンドウ
========================= */
.memo-editor{
  margin:15px 0;
}

.memo-editor-row{
  display:grid;
  grid-template-columns:repeat(4,minmax(0,1fr));
  gap:8px;
  margin-bottom:8px;
}

.memo-editor-button{
  min-width:0;
  padding:9px 8px;
  border:1px solid var(--border);
  border-radius:7px;
  background:var(--panelBg);
  color:var(--panelText);
  cursor:pointer;
  font-family:var(--commentFont);
  font-size:var(--commentSize);
  text-align:left;
  overflow:hidden;
  white-space:nowrap;
  text-overflow:ellipsis;
}

.memo-editor-button:hover{
  background:var(--light);
}

.memo-window{
  position:fixed;
  z-index:10000;
  width:300px;
  height:430px;
  left:50%;
  top:50%;
  transform:translate(-50%,-50%);
  background:var(--panelBg);
  color:var(--panelText);
  border:1px solid #999;
  border-radius:10px;
  box-shadow:0 8px 30px #0005;
  display:flex;
  flex-direction:column;
  overflow:hidden;
}


.memo-window-header{
  height:42px;
  flex:0 0 42px;
  display:flex;
  align-items:center;
  justify-content:space-between;
  padding:0 8px 0 12px;
  background:var(--main);
  color:white;
  cursor:move;
  user-select:none;
}

.memo-window-title{
  font-weight:bold;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}

.memo-window-close{
  width:30px;
  height:30px;
  border:0;
  border-radius:6px;
  background:transparent;
  color:white;
  font-size:22px;
  line-height:30px;
  cursor:pointer;
  padding:0;
}

.memo-window-close:hover{
  background:#ffffff33;
}

.memo-window-body{
  flex:1;
  min-height:0;
  padding:12px;
  display:flex;
}

.memo-window-body textarea{
  width:100%;
  height:100%;
  min-height:0;
  resize:none;
  margin:0;
  font-family:var(--commentFont);
  font-size:var(--commentSize);
  line-height:1.7;
}

.memo-view-grid{
  display:grid;
  grid-template-columns:repeat(4,minmax(0,1fr));
  gap:8px;
  margin:10px 0 18px;
}

.memo-view-item{
  min-width:0;
  height:38px;
  box-sizing:border-box;
  padding:8px 10px;
  border:1px solid var(--border);
  border-radius:7px;
  background:var(--panelBg);
  color:var(--panelText);
  font-family:var(--commentFont);
  font-size:var(--commentSize);
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}

@media(max-width:700px){
  .memo-view-grid{
    grid-template-columns:repeat(4,minmax(0,1fr));
    gap:5px;
  }

  .memo-view-item{
    padding:7px 5px;
    font-size:12px;
  }
}

@media(max-width:700px){
  .memo-window{
    width:280px;
    height:400px;
  }
}

.photo-window img{max-width:95vw;max-height:95vh;object-fit:contain}
.storage-info{
  padding:10px;margin:10px 0;border-radius:8px;background:#eef5ff;
  color:#315b8a;font-size:14px
}

/* =========================
   ダークモード
   背景カラーそのものには適用しない
========================= */
body.dark-mode{
  --text:#eeeeee;
  --panelText:#eeeeee;
  --inputBg:#292929;
  --inputText:#eeeeee;
  --border:#555;
  --light:#303b48;
  --panelBg:rgba(30,30,30,.96);
}

/* ダークモード:指定した入力欄の案内文字だけ明るくする */
body.dark-mode #tripTitle::placeholder,
body.dark-mode #tripStation::placeholder,
body.dark-mode #tripTravelMinutes::placeholder,
body.dark-mode [id^="tripPurpose"]::placeholder,
body.dark-mode #tripText::placeholder,
body.dark-mode input[type="search"]::placeholder{
  color:#bdbdbd;
  opacity:1;
}

body.dark-mode .sidebar{
  border-right-color:#444
}
body.dark-mode .side-btn{
  color:#eeeeee
}
body.dark-mode .side-btn:hover{
  background:#303b48
}
body.dark-mode .panel,
body.dark-mode .detail,
body.dark-mode .settings-section{
  box-shadow:0 2px 9px #0008
}

body.dark-mode .cancel{
  background:#555;
  color:#eee
}
body.dark-mode .trip{
  border-bottom-color:#444
}
body.dark-mode .trip-date{
  color:#aaa
}
body.dark-mode .photo-count-editor{
  border-color:#444;
  background:rgba(45,45,45,.8)
}
body.dark-mode .photo-count-editor-button,
body.dark-mode .year-buttons button,
body.dark-mode .size-button,
body.dark-mode .font-choice,
body.dark-mode .transparency-button,
body.dark-mode .dark-mode-button{
  border-color:#555
}
body.dark-mode .font-choice-name{
  color:#ccc
}
body.dark-mode .size-setting{
  border-bottom-color:#444
}
body.dark-mode .detail-text{
  border-color:#444
}
body.dark-mode .photo-item,
body.dark-mode .edit-photo{
  border-color:#666
}
body.dark-mode .edit-comment-box{
  border-color:#555
}
body.dark-mode .storage-info{
  background:#26384c;
  color:#c8ddf5
}

@media(max-width:700px){
  .layout{display:block}
  .sidebar{
    width:100%;flex-direction:row;overflow-x:auto;
    border-right:0;border-bottom:1px solid #ddd
  }
  .side-btn{width:auto;min-width:max-content;white-space:nowrap}
  main{padding:12px}
  .trip{display:block}
  .trip-date{width:auto;margin-bottom:4px}
  .photo-grid,.edit-photo-grid{
    grid-template-columns:repeat(var(--photoColumns),minmax(0,1fr));
  }
  .editor-heading-title{font-size:22px}
  .date-row{align-items:center;flex-wrap:nowrap}
  .date-row input{min-width:0;width:auto}
  .weekday-display{flex-shrink:0}
}

/* =========================
   起動時・フォルダアクセス許可画面
========================= */

.permission-screen{
  min-height:100vh;
  width:100%;
  display:flex;
  align-items:center;
  justify-content:center;
  position:relative;
  overflow:hidden;
  padding:30px;

  background:
    radial-gradient(
      circle at 15% 25%,
      rgba(80,140,255,.42),
      transparent 35%
    ),
    radial-gradient(
      circle at 85% 20%,
      rgba(155,90,255,.36),
      transparent 35%
    ),
    radial-gradient(
      circle at 70% 85%,
      rgba(60,210,220,.28),
      transparent 40%
    ),
    linear-gradient(
      135deg,
      #eaf2ff 0%,
      #edf0ff 45%,
      #f4edff 100%
    );
}

.permission-screen::before{
  content:"";
  position:absolute;
  width:420px;
  height:420px;
  border-radius:50%;
  background:rgba(255,255,255,.35);
  filter:blur(2px);
  top:-180px;
  right:-120px;
}

.permission-screen::after{
  content:"";
  position:absolute;
  width:320px;
  height:320px;
  border-radius:50%;
  background:rgba(79,134,198,.08);
  filter:blur(4px);
  bottom:-160px;
  left:-100px;
}

.permission-card{
  position:relative;
  z-index:1;
  width:min(520px,100%);
  padding:38px 34px;
  border:1px solid rgba(255,255,255,.8);
  border-radius:24px;
  background:rgba(255,255,255,.78);
  box-shadow:
    0 20px 60px rgba(40,70,100,.16),
    0 4px 16px rgba(0,0,0,.06);
  backdrop-filter:blur(14px);
  -webkit-backdrop-filter:blur(14px);
  text-align:center;
}

.permission-icon{
  width:72px;
  height:72px;
  margin:0 auto 20px;
  display:flex;
  align-items:center;
  justify-content:center;
  border-radius:20px;
  background:linear-gradient(135deg,var(--main),#76a9df);
  color:white;
  font-size:34px;
  box-shadow:0 10px 25px rgba(79,134,198,.28);
}

.permission-card h2{
  margin:0 0 14px;
  color:var(--main);
  font-family:var(--settingFont);
  font-size:var(--settingSize);
  font-weight:bold;
}

.permission-card p{
  margin:0 0 24px;
  color:#666;
  font-family:var(--settingFont);
  font-size:var(--settingSize);
  line-height:1.8;
}

.permission-card .action{
  margin:0;
}

body.dark-mode .permission-screen{
  background:
    radial-gradient(
      circle at 20% 20%,
      rgba(79,134,198,.25),
      transparent 35%
    ),
    radial-gradient(
      circle at 80% 80%,
      rgba(112,173,71,.12),
      transparent 35%
    ),
    linear-gradient(
      135deg,
      #171d25,
      #20262e 50%,
      #18211d
    );
}

body.dark-mode .permission-card{
  background:rgba(30,30,30,.78);
  border-color:rgba(255,255,255,.08);
  box-shadow:0 20px 60px rgba(0,0,0,.35);
}

body.dark-mode .permission-card p{
  color:#bbb;
}

</style>
</head>

<body>

<header><h1 id="appTitle">記録</h1></header>

<div class="layout">
<aside class="sidebar">
  <button class="side-btn active" data-page="years">📅 一覧</button>
  <button class="side-btn" data-page="add">➕ 編集</button>
  <button class="side-btn" data-page="settings">⚙ 設定</button>
  <button class="side-btn" data-page="background">🖼 背景画像</button>
  <div class="sidebar-space"></div>
<button class="side-btn" data-page="initialize">🗑 記録初期化</button>

</aside>
<main id="main"></main>
</div>

<script>
"use strict";

/* =========================
   保存キー
========================= */
const PHOTOS_DIR="photos";
const BACKGROUND_DIR="background";

let recordFolderHandle=null;

const DB_NAME="record_v3_image_db";
const DB_VERSION=1;
const IMAGE_STORE="images";

let dbPromise=null;

function openDB(){
  if(dbPromise)return dbPromise;
  dbPromise=new Promise((resolve,reject)=>{
    if(!window.indexedDB){
      reject(new Error("このブラウザはIndexedDBに対応していません。"));
      return;
    }
    const r=indexedDB.open(DB_NAME,DB_VERSION);
    r.onerror=()=>reject(r.error||new Error("IndexedDBを開けませんでした。"));
    r.onupgradeneeded=e=>{
      const db=e.target.result;
      if(!db.objectStoreNames.contains(IMAGE_STORE))
        db.createObjectStore(IMAGE_STORE);
    };
    r.onsuccess=()=>resolve(r.result);
  });
  return dbPromise;
}

async function idbPut(key,value){
  const db=await openDB();
  return new Promise((resolve,reject)=>{
    const tx=db.transaction(IMAGE_STORE,"readwrite");
    tx.objectStore(IMAGE_STORE).put(value,key);
    tx.oncomplete=resolve;
    tx.onerror=()=>reject(tx.error);
  });
}

async function idbGet(key){
  const db=await openDB();
  return new Promise((resolve,reject)=>{
    const tx=db.transaction(IMAGE_STORE,"readonly");
    const r=tx.objectStore(IMAGE_STORE).get(key);
    r.onsuccess=()=>resolve(r.result);
    r.onerror=()=>reject(r.error);
  });
}

async function getPhotosFolder(){
  if(!recordFolderHandle){
    throw new Error("記録管理フォルダが選択されていません。");
  }

  return await recordFolderHandle.getDirectoryHandle(
    PHOTOS_DIR,
    {create:true}
  );
}

async function savePhotoToFolder(imageId,blob){
  const folder=await getPhotosFolder();

  const fileHandle=await folder.getFileHandle(
    imageId+".jpg",
    {create:true}
  );

  const writable=await fileHandle.createWritable();

  await writable.write(blob);

  await writable.close();
}

async function loadPhotoFromFolder(imageId){
  const folder=await getPhotosFolder();

  const fileHandle=await folder.getFileHandle(
    imageId+".jpg"
  );

  return await fileHandle.getFile();
}

async function deletePhotoFromFolder(imageId){
  if(!imageId)return;

  try{
    const folder=await getPhotosFolder();

    await folder.removeEntry(
      imageId+".jpg"
    );

  }catch(e){
    if(e.name!=="NotFoundError"){
      console.error("写真削除エラー:",e);
      throw e;
    }
  }
}

/* =========================
   設定
========================= */
const DEFAULT_COLORS=[
  "#4f86c6","#5b9bd5","#70ad47","#ed7d31","#a64d79",
  "#8064a2","#00a6a6","#c0504d","#fef08a","#808080",
  "#f01818","#ff4f9a","#9b35e8","#00b894","#333333",
  "#ffffff"
];

const FONTS=[
  ["游ゴシック","Yu Gothic"],
  ["メイリオ","Meiryo"],
  ["BIZ UDPゴシック","'BIZ UDPGothic',sans-serif"],
  ["BIZ UDP明朝","'BIZ UDMincho',serif"],
  ["UD デジタル 教科書体","'UD Digi Kyokasho NK-R',sans-serif"],
  ["MS P ゴシック","MS PGothic"],
  ["MS P 明朝","MS PMincho"]
];

/*
  ここは写真の「最大枚数」ではなく、
  「1行あたりの列数」。
*/
const PHOTO_COLUMNS={2:2,3:3,4:4,5:5,6:6,8:8,10:10};
const PHOTO_COUNT_OPTIONS=[10,8,6,5,4,3,2];
const DEFAULT_PHOTO_COLUMNS=3;

const DEFAULT_SETTINGS={
  title:"記録",
  colors:[...DEFAULT_COLORS],
  colorIndex:0,

  backgroundColors:[...DEFAULT_COLORS],
  backgroundColorIndex:0,

  darkMode:false,

  titleSize:3,
  dateSize:2,
  editSize:2,
  bodySize:2,
  commentSize:2,
  listSize:2,
  settingSize:2,

  titleFont:"游ゴシック",
  dateFont:"游ゴシック",
  editFont:"游ゴシック",
  bodyFont:"游ゴシック",
  commentFont:"游ゴシック",
  listFont:"游ゴシック",
  settingFont:"游ゴシック",

  font:"游ゴシック",
  titleStyle:"classic",
 photoColumns:DEFAULT_PHOTO_COLUMNS,
panelTransparency:1,

backgroundImageId:null

};

function normalizePhotoColumns(v){
  const n=Number(v);
  return PHOTO_COLUMNS[n]?n:DEFAULT_PHOTO_COLUMNS;
}

function setPhotoColumns(n){
  n=normalizePhotoColumns(n);
  document.documentElement.style.setProperty("--photoColumns",n);
  return n;
}

function normalizeColors(colors){
  let result=Array.isArray(colors)?[...colors]:[];

  result=result.map(c=>c==="#fff2a8"?"#d9a900":c);

  DEFAULT_COLORS.forEach(c=>{
    if(!result.includes(c))result.push(c);
  });

  return result;
}

async function loadSettings(){
  try{
    if(!recordFolderHandle){
      return {
        ...DEFAULT_SETTINGS,
        colors:[...DEFAULT_COLORS],
        backgroundColors:[...DEFAULT_COLORS]
      };
    }
 
    const fileHandle=await recordFolderHandle.getFileHandle(
      "settings.json"
    );

    const file=await fileHandle.getFile();
    const raw=await file.text();

    const data=JSON.parse(raw);

    const colors=normalizeColors(data.colors);

    const backgroundColors=normalizeColors(
      Array.isArray(data.backgroundColors)
        ?data.backgroundColors
        :colors
    );

    const s={
      ...DEFAULT_SETTINGS,
      ...data,
      colors,
      backgroundColors
    };

    const old=data.font||"游ゴシック";

[
  "titleFont",
  "dateFont",
  "editFont",
  "bodyFont",
  "commentFont",
  "listFont",
  "settingFont"
].forEach(k=>{
  if(
    !data[k] ||
    !FONTS.some(f=>f[0]===data[k])
  ){
    s[k]=old;
  }
});

    s.titleStyle="classic";

    if(s.colorIndex<0||s.colorIndex>=s.colors.length)s.colorIndex=0;

    if(
      s.backgroundColorIndex<0||
      s.backgroundColorIndex>=s.backgroundColors.length
    )s.backgroundColorIndex=0;

    s.darkMode=!!s.darkMode;

s.backgroundImageId=s.backgroundImageId||null;
backgroundImageId=s.backgroundImageId;

return s;

  }catch(e){
    console.error(e);

    return {
      ...DEFAULT_SETTINGS,
      colors:[...DEFAULT_COLORS],
      backgroundColors:[...DEFAULT_COLORS]
    };
  }
}

async function saveSettings(){
  try{
    if(!recordFolderHandle){
      alert("記録管理フォルダを選択してください。");
      return false;
    }

    let permission=await recordFolderHandle.queryPermission({
      mode:"readwrite"
    });

    if(permission!=="granted"){
      permission=await recordFolderHandle.requestPermission({
        mode:"readwrite"
      });
    }

    if(permission!=="granted"){
      alert("記録管理フォルダへの書き込みを許可してください。");
      return false;
    }

    const fileHandle=await recordFolderHandle.getFileHandle(
      "settings.json",
      {create:true}
    );

    settings.backgroundImageId=backgroundImageId||null;

const writable=await fileHandle.createWritable();

await writable.write(JSON.stringify(settings,null,2));

    await writable.close();

    return true;

  }catch(e){
    console.error(e);
    alert("設定を保存できませんでした。");
    return false;
  }
}

async function selectRecordFolder(){
  try{
    recordFolderHandle=await window.showDirectoryPicker({
      mode:"readwrite"
    });

    const permission=await recordFolderHandle.requestPermission({
      mode:"readwrite"
    });

    if(permission!=="granted"){
      recordFolderHandle=null;
      alert("記録管理フォルダへのアクセスを許可してください。");
      return false;
    }

    await idbPut("__record_folder_handle__",recordFolderHandle);

    return true;

  }catch(e){
    if(e.name==="AbortError")return false;
    console.error(e);
    alert("記録管理フォルダを選択できませんでした。");
    return false;
  }
}

/* =========================
   記録データ
========================= */
function normalizeTrips(list){
  return (Array.isArray(list)?list:[]).map(t=>({
    id:t.id||Date.now()+"_"+Math.random(),
    title:t.title||"",
    date:t.date||"",
    text:t.text||"",

    prefecture:t.prefecture||"",
    station:t.station||"",
    travelMinutes:t.travelMinutes??"",

    purposes:[1,2,3].map((n,i)=>{
      const p=Array.isArray(t.purposes)
        ?(t.purposes[i]||{})
        :{};

      return {
        purpose:p.purpose||"",
        startMonth:p.startMonth||"",
        startPeriod:p.startPeriod||"",
        endMonth:p.endMonth||"",
        endPeriod:p.endPeriod||""
      };
    }),

    memos:Array.isArray(t.memos)
      ?[...t.memos,"","","","","","",""].slice(0,8)
      :["","","","","","","",""],

    photoColumns:normalizePhotoColumns(t.photoColumns),

    photos:Array.isArray(t.photos)
      ?t.photos.filter(p=>p&&p.imageId).map(p=>({
        id:p.id||Date.now()+"_"+Math.random(),
        imageId:p.imageId,
        comment:p.comment||""
      }))
      :[]
  }));
}

async function loadTrips(){
  try{
    if(!recordFolderHandle)return [];

    const fileHandle=await recordFolderHandle.getFileHandle(
      "records.json"
    );

    const file=await fileHandle.getFile();
    const raw=await file.text();

    return raw?normalizeTrips(JSON.parse(raw)):[];
  }catch(e){
    console.error(e);
    return [];
  }
}

async function saveTrips(){
  try{
    if(!recordFolderHandle){
      alert("記録管理フォルダを選択してください。");
      return false;
    }

    let permission=await recordFolderHandle.queryPermission({
      mode:"readwrite"
    });

    if(permission!=="granted"){
      permission=await recordFolderHandle.requestPermission({
        mode:"readwrite"
      });
    }

    if(permission!=="granted"){
      alert("記録管理フォルダへの書き込みを許可してください。");
      return false;
    }

    const fileHandle=await recordFolderHandle.getFileHandle(
      "records.json",
      {create:true}
    );

    const writable=await fileHandle.createWritable();

    await writable.write(
      JSON.stringify(trips,null,2)
    );

    await writable.close();

    return true;

  }catch(e){
    console.error("saveTrips error:",e);
    alert(
      "保存できませんでした。\n\n"+
      "records.json の作成・保存に失敗しました。\n"+
      e.name+": "+e.message
    );
    return false;
  }
}

async function initializeRecords(){
  const first=confirm(
    "記録を初期化します。\n\n"+
    "すべての記録と、記録に保存されている写真が削除されます。\n\n"+
    "設定・背景画像・背景画像の設定は削除されません。\n\n"+
    "本当に実行しますか?"
  );

  if(!first)return;

  const second=confirm(
    "最終確認です。\n\n"+
    "記録と記録に紐づく写真をすべて削除します。\n"+
    "この操作は元に戻せません。\n\n"+
    "実行しますか?"
  );

  if(!second)return;

  try{
    for(const trip of trips){
      if(!Array.isArray(trip.photos))continue;

      for(const photo of trip.photos){
        if(photo && photo.imageId){
          await deletePhotoFromFolder(photo.imageId);
        }
      }
    }

    trips=[];

    if(!(await saveTrips())){
      return;
    }

    showYears();

    alert("記録と写真を初期化しました。");

  }catch(e){
    console.error("記録初期化エラー:",e);

    alert(
      "記録の初期化中にエラーが発生しました。\n\n"+
      e.name+": "+e.message
    );
  }
}

/* =========================
   下書き
========================= */
let draftTimer=null;

function saveDraft(){
  clearTimeout(draftTimer);
  draftTimer=setTimeout(saveDraftNow,300);
}

function getCurrentDraft(){
  const getValue=id=>
    document.getElementById(id)?.value||"";

  const title=getValue("tripTitle");
  const date=getValue("tripDate");
  const text=getValue("tripText");

  const prefecture=getValue("tripPrefecture");
  const station=getValue("tripStation");
  const travelMinutes=getValue("tripTravelMinutes");

  const purposes=[1,2,3].map(n=>({
    purpose:getValue("tripPurpose"+n),
    startMonth:getValue("tripStartMonth"+n),
    startPeriod:getValue("tripStartPeriod"+n),
    endMonth:getValue("tripEndMonth"+n),
    endPeriod:getValue("tripEndPeriod"+n)
  }));

  const hasTravelInfo=
    prefecture||
    station||
    travelMinutes||
    purposes.some(p=>
      p.purpose||
      p.startMonth||
      p.startPeriod||
      p.endMonth||
      p.endPeriod
    );

  const hasMemos=Array.isArray(editingMemos)
    &&editingMemos.some(m=>String(m||"").trim()!=="");

  if(
    !title&&!date&&!text&&!hasTravelInfo&&
    !editingPhotos.length&&!hasMemos
  ){
    return null;
  }

  return {
    editingId,
    title,
    date,
    text,
    prefecture,
    station,
    travelMinutes,
    purposes,
    memos:Array.isArray(editingMemos)
      ?[...editingMemos,"","","","","","",""].slice(0,8)
      :["","","","","","","",""],
    photoColumns:normalizePhotoColumns(editingPhotoColumns),
    photos:editingPhotos.map(p=>({
      id:p.id,
      imageId:p.imageId,
      comment:p.comment||""
    })),
    savedAt:new Date().toISOString()
  };
}

async function saveDraftNow(){
  const d=getCurrentDraft();

  if(!d){
    await clearDraft();
    return;
  }

  try{
    if(!recordFolderHandle)return;

    let permission=await recordFolderHandle.queryPermission({
      mode:"readwrite"
    });

    if(permission!=="granted"){
      permission=await recordFolderHandle.requestPermission({
        mode:"readwrite"
      });
    }

    if(permission!=="granted")return;

    const fileHandle=await recordFolderHandle.getFileHandle(
      "draft.json",
      {create:true}
    );

    const writable=await fileHandle.createWritable();

    await writable.write(
      JSON.stringify(d,null,2)
    );

    await writable.close();

  }catch(e){
    console.error("下書き保存エラー:",e);
  }
}

async function saveDraftBeforeNavigation(){
  const d=getCurrentDraft();

  if(!d){
    await clearDraft();
    return;
  }

  // 編集中の候補が保存済みデータと同じか確認
  const original=trips.find(
    t=>String(t.id)===String(editingId)
  );

  if(original && String(d.editingId)===String(original.id)){
    const normalizePurposes=items=>
      [0,1,2].map(i=>{
        const p=(items||[])[i]||{};
        return {
          purpose:String(p.purpose||""),
          startMonth:String(p.startMonth||""),
          startPeriod:String(p.startPeriod||""),
          endMonth:String(p.endMonth||""),
          endPeriod:String(p.endPeriod||"")
        };
      });

    const normalizeMemos=items=>
      [...(Array.isArray(items)?items:[]),"","","","","","",""]
        .slice(0,8)
        .map(v=>String(v||""));

    const normalizePhotos=items=>
      (Array.isArray(items)?items:[]).map(p=>({
        id:p.id,
        imageId:p.imageId,
        comment:p.comment||""
      }));

    const current={
      title:String(d.title||""),
      date:String(d.date||""),
      text:String(d.text||""),
      prefecture:String(d.prefecture||""),
      station:String(d.station||""),
      travelMinutes:String(d.travelMinutes||""),
      purposes:normalizePurposes(d.purposes),
      memos:normalizeMemos(d.memos),
      photoColumns:normalizePhotoColumns(d.photoColumns),
      photos:normalizePhotos(d.photos)
    };

    const saved={
      title:String(original.title||""),
      date:String(original.date||""),
      text:String(original.text||""),
      prefecture:String(original.prefecture||""),
      station:String(original.station||""),
      travelMinutes:String(original.travelMinutes||""),
      purposes:normalizePurposes(original.purposes),
      memos:normalizeMemos(original.memos),
      photoColumns:normalizePhotoColumns(original.photoColumns),
      photos:normalizePhotos(original.photos)
    };

    if(JSON.stringify(current)===JSON.stringify(saved)){
      await clearDraft();
      return;
    }
  }

  // 変更がある場合は従来どおり下書きを保存
  try{
    if(!recordFolderHandle)return;

    let permission=await recordFolderHandle.queryPermission({
      mode:"readwrite"
    });

    if(permission!=="granted"){
      permission=await recordFolderHandle.requestPermission({
        mode:"readwrite"
      });
    }

    if(permission!=="granted")return;

    const fileHandle=await recordFolderHandle.getFileHandle(
      "draft.json",
      {create:true}
    );

    const writable=await fileHandle.createWritable();

    await writable.write(
      JSON.stringify(d,null,2)
    );

    await writable.close();

  }catch(e){
    console.error("画面移動前の下書き保存エラー:",e);
  }
}

async function clearDraft(){
  clearTimeout(draftTimer);

  try{
    if(!recordFolderHandle)return;

    let permission=await recordFolderHandle.queryPermission({
      mode:"readwrite"
    });

    if(permission!=="granted"){
      permission=await recordFolderHandle.requestPermission({
        mode:"readwrite"
      });
    }

    if(permission!=="granted")return;

    try{
      await recordFolderHandle.removeEntry("draft.json");
    }catch(e){
      if(e.name!=="NotFoundError"){
        throw e;
      }
    }

  }catch(e){
    console.error("下書き削除エラー:",e);
  }
}

async function loadDraft(){
  try{
    if(!recordFolderHandle)return null;

    let permission=await recordFolderHandle.queryPermission({
      mode:"read"
    });

    if(permission!=="granted"){
      permission=await recordFolderHandle.requestPermission({
        mode:"read"
      });
    }

    if(permission!=="granted")return null;

    const fileHandle=await recordFolderHandle.getFileHandle(
      "draft.json"
    );

    const file=await fileHandle.getFile();
    const raw=await file.text();

    return raw ? JSON.parse(raw) : null;

  }catch(e){
    if(e.name!=="NotFoundError"){
      console.error("下書き読み込みエラー:",e);
    }
    return null;
  }
}

/* =========================
   状態
========================= */
let settings={...DEFAULT_SETTINGS};
let trips=[];
let editingId=null;
let editingPhotos=[];
let editingPhotoColumns=DEFAULT_PHOTO_COLUMNS;
let saveLock=false;
let backgroundImageId=null;
let backgroundObjectUrl=null;
let editingMemos=["","","","","","","",""];

/* =========================
   ナビ
========================= */
document.querySelectorAll(".side-btn").forEach(b=>{
  b.onclick=async()=>{
    const p=b.dataset.page;

    if(p!=="add")saveDraftBeforeNavigation();

    if(p==="years")showYears();
    if(p==="add")showAdd();
    if(p==="settings")showSettings();
    if(p==="background")showBackground();

    if(p==="initialize"){
      await initializeRecords();
    }
  };
});

function setActive(page){
  document.querySelectorAll(".side-btn").forEach(b=>
    b.classList.toggle("active",b.dataset.page===page)
  );
}

/* =========================
   一覧
========================= */
function showYears(){
  setActive("years");

  const main=document.getElementById("main");
  main.innerHTML="";

  const panel=document.createElement("div");
  panel.className="panel";

  const monthSet=new Set();
  const timeSet=new Set();

  function eachPurpose(t){
    return Array.isArray(t.purposes)?t.purposes:[];
  }

  function purposeMonths(p){
    const start=Number(p.startMonth);
    const end=Number(p.endMonth);

    if(!start)return [];
    if(!end)return [start];

    const result=[];
    let m=start;

    for(let i=0;i<12;i++){
      result.push(m);
      if(m===end)break;
      m=m%12+1;
    }

    return result;
  }

  trips.forEach(t=>{
    eachPurpose(t).forEach(p=>{
      purposeMonths(p).forEach(m=>monthSet.add(m));
    });

    if(t.travelMinutes!==""&&t.travelMinutes!=null){
      const n=Number(t.travelMinutes);
      if(Number.isFinite(n)&&n>=0)timeSet.add(n);
    }
  });

  const months=[...monthSet].sort((a,b)=>a-b);
  const maxTime=timeSet.size?Math.max(...timeSet):0;
const timeRanges=[];

for(let start=0;start<=maxTime;){
  const end=start===0?15:start+14;

  if([...timeSet].some(n=>n>=start&&n<=end)){
    timeRanges.push({start,end});
  }

  start=start===0?16:start+15;
}

  const selectedMonths=new Set();
  const selectedTimes=new Set();
  let searchText="";
  let monthOpen=false;
  let timeOpen=false;

  const filterBar=document.createElement("div");
  filterBar.style.display="flex";
  filterBar.style.flexWrap="wrap";
  filterBar.style.alignItems="center";
  filterBar.style.gap="8px";
  filterBar.style.marginBottom="14px";

  function makeButton(label,onClick){
    const b=document.createElement("button");
    b.type="button";
    b.textContent=label;
    b.style.padding="8px 12px";
    b.style.border="1px solid var(--border)";
    b.style.borderRadius="7px";
    b.style.background="var(--panelBg)";
    b.style.color="var(--panelText)";
    b.style.cursor="pointer";
    b.onclick=onClick;
    return b;
  }

  const monthButton=makeButton("時期 ▽",()=>{
    monthOpen=!monthOpen;
    renderFilters();
  });

  const monthClear=makeButton("解除",()=>{
    selectedMonths.clear();
    renderFilters();
    renderRows();
  });

  const timeButton=makeButton("時間 ▽",()=>{
    timeOpen=!timeOpen;
    renderFilters();
  });
monthClear.style.marginRight="15px";

  const timeClear=makeButton("解除",()=>{
  selectedTimes.clear();
  renderFilters();
  renderRows();
});
timeClear.style.marginRight="30px";

  const search=document.createElement("input");
  search.type="search";
  search.placeholder="検索";
  search.style.flex="1";
  search.style.minWidth="120px";
  search.style.margin="0";
  search.addEventListener("input",()=>{
    searchText=search.value.trim().toLowerCase();
    renderRows();
  });

  const count=document.createElement("span");
  count.style.whiteSpace="nowrap";

  filterBar.append(
    monthButton,monthClear,timeButton,timeClear,search,count
  );
  panel.appendChild(filterBar);

  const monthOptions=document.createElement("div");
  monthOptions.style.display="none";
  monthOptions.style.flexWrap="wrap";
  monthOptions.style.gap="6px";
  monthOptions.style.marginBottom="12px";

  const timeOptions=document.createElement("div");
  timeOptions.style.display="none";
  timeOptions.style.flexWrap="wrap";
  timeOptions.style.gap="6px";
  timeOptions.style.marginBottom="12px";

  panel.append(monthOptions,timeOptions);

  const list=document.createElement("div");
  list.className="trip-list";
  panel.appendChild(list);

  function renderFilters(){
    monthButton.textContent=monthOpen?"時期 ▲":"時期 ▽";
    timeButton.textContent=timeOpen?"時間 ▲":"時間 ▽";

    monthButton.style.background=monthOpen?"var(--main)":"var(--panelBg)";
    monthButton.style.color=monthOpen?"white":"var(--panelText)";
    timeButton.style.background=timeOpen?"var(--main)":"var(--panelBg)";
    timeButton.style.color=timeOpen?"white":"var(--panelText)";

    monthOptions.style.display=monthOpen?"flex":"none";
    timeOptions.style.display=timeOpen?"flex":"none";

    monthOptions.innerHTML="";
    months.forEach(m=>{
      const b=makeButton(String(m).padStart(2,"0")+"月",()=>{
        if(selectedMonths.has(m))selectedMonths.delete(m);
        else selectedMonths.add(m);
        renderFilters();
        renderRows();
      });

      if(selectedMonths.has(m)){
        b.style.background="var(--main)";
        b.style.color="white";
      }

      monthOptions.appendChild(b);
    });

    timeOptions.innerHTML="";
    timeRanges.forEach(r=>{
      const key=r.start+"-"+r.end;
      const label=String(r.start).padStart(2,"0")+"-"+
        String(r.end).padStart(2,"0")+"分";

      const b=makeButton(label,()=>{
        if(selectedTimes.has(key))selectedTimes.delete(key);
        else selectedTimes.add(key);
        renderFilters();
        renderRows();
      });

      if(selectedTimes.has(key)){
        b.style.background="var(--main)";
        b.style.color="white";
      }

      timeOptions.appendChild(b);
    });
  }

  function matchesMonth(t){
    if(!selectedMonths.size)return true;

    return eachPurpose(t).some(p=>
      purposeMonths(p).some(m=>selectedMonths.has(m))
    );
  }

  function matchesTime(t){
    if(!selectedTimes.size)return true;

    if(t.travelMinutes===""||t.travelMinutes==null)return false;

    const n=Number(t.travelMinutes);

    return [...selectedTimes].some(key=>{
      const [start,end]=key.split("-").map(Number);
      return n>=start&&n<=end;
    });
  }

  function matchesSearch(t){
    if(!searchText)return true;

    const values=[
      t.title,t.prefecture,t.station,t.text,
      ...eachPurpose(t).map(p=>p.purpose)
    ];

    return values.some(v=>
      String(v||"").toLowerCase().includes(searchText)
    );
  }

  function renderRows(){
    list.innerHTML="";

    const filtered=trips.filter(t=>
      matchesMonth(t)&&matchesTime(t)&&matchesSearch(t)
    );

    filtered.sort((a,b)=>{
      function order(t){
        const p=eachPurpose(t).find(x=>x.startMonth);
        return p
          ?Number(p.startMonth)*100+
            ({"上旬":1,"中旬":2,"下旬":3}[p.startPeriod]||0)
          :9999;
      }

      return order(a)-order(b);
    });

    count.textContent=filtered.length+"件";

    if(!filtered.length){
      const empty=document.createElement("p");
      empty.textContent="該当する候補がありません。";
      list.appendChild(empty);
      return;
    }

    filtered.forEach(t=>{
      const row=document.createElement("div");
      row.className="trip";
      row.style.display="grid";
      row.style.gridTemplateColumns="max-content 85px minmax(0,1fr)";
row.style.gap="9px";
      row.style.alignItems="center";

      const p=eachPurpose(t).find(x=>
        x.startMonth||x.startPeriod
      );

      const period=document.createElement("div");
      period.className="trip-date";
      period.style.width="auto";
      period.style.minWidth="0";

      period.textContent=p&&p.startMonth
        ?String(p.startMonth).padStart(2,"0")+"月"+
          (p.startPeriod||"")+"~"
        :"時期未設定";

          period.style.fontWeight="600";
          period.style.color="var(--main)";
          period.style.border="1px solid var(--main)";
          period.style.borderRadius="5px";
          period.style.padding="2px 4px";
          period.style.width="fit-content";

      const time=document.createElement("div");
      time.className="trip-date";
      time.style.width="auto";
      time.style.minWidth="0";
      time.style.textAlign="center";

      time.textContent=t.travelMinutes!==""&&t.travelMinutes!=null
        ?String(t.travelMinutes).padStart(3,"0")+" 分"
        :"--- 分";

          time.style.fontWeight="600";
          time.style.color="var(--main)";
          time.style.border="1px solid var(--main)";
          time.style.borderRadius="5px";
          time.style.padding="2px 4px";
          time.style.width="fit-content";

      const title=document.createElement("div");
      title.className="trip-title";
      title.style.minWidth="0";
      title.style.overflowWrap="anywhere";

      title.textContent=t.title||"";

      row.append(period,time,title);
      row.onclick=()=>editTrip(t.id);
      list.appendChild(row);
    });
  }

  renderFilters();
  renderRows();
  main.appendChild(panel);
}

function showYear(){
  showYears();
}

/* =========================
   追加・編集
========================= */
async function showAdd(){
  setActive("add");
  editingId=null;
  editingPhotos=[];
  editingPhotoColumns=DEFAULT_PHOTO_COLUMNS;
  editingMemos=["","","","","","","",""];

  const draft=await loadDraft();

  if(draft){
    const valid=[];

    for(const p of draft.photos||[]){
      if(!p.imageId)continue;

      try{
        await loadPhotoFromFolder(p.imageId);

        valid.push({
          id:p.id,
          imageId:p.imageId,
          comment:p.comment||""
        });
      }catch(e){
        console.error("下書き写真を読み込めません:",e);
      }
    }

    editingId=draft.editingId??null;
    editingPhotos=valid;
    editingPhotoColumns=normalizePhotoColumns(draft.photoColumns);

    editingMemos=Array.isArray(draft.memos)
      ?[...draft.memos,"","","","","","",""].slice(0,8)
      :["","","","","","","",""];

    renderEditor(
      draft.title||"",
      draft.date||today(),
      draft.text||"",
      true
    );

  }else{
    renderEditor("","","",false);
  }

  const setValue=(id,value)=>{
    const el=document.getElementById(id);
    if(el)el.value=value??"";
  };

  setValue("tripPrefecture",draft?.prefecture);
  setValue("tripStation",draft?.station);
  setValue("tripTravelMinutes",draft?.travelMinutes);

  const purposes=Array.isArray(draft?.purposes)
    ?draft.purposes:[];

  [1,2,3].forEach(n=>{
    const p=purposes[n-1]||{};

    setValue("tripPurpose"+n,p.purpose);
    setValue("tripStartMonth"+n,p.startMonth);
    setValue("tripStartPeriod"+n,p.startPeriod);
    setValue("tripEndMonth"+n,p.endMonth);
    setValue("tripEndPeriod"+n,p.endPeriod);
  });

  [
    "tripPrefecture",
    "tripStation",
    "tripTravelMinutes",
    ...[1,2,3].flatMap(n=>[
      "tripPurpose"+n,
      "tripStartMonth"+n,
      "tripStartPeriod"+n,
      "tripEndMonth"+n,
      "tripEndPeriod"+n
    ])
  ].forEach(id=>{
    const el=document.getElementById(id);
    if(el){
      el.addEventListener("input",saveDraft);
      el.addEventListener("change",saveDraft);
    }
  });
}

function openMemoEditor(index){
  const current=editingMemos[index]||"";

  document.querySelector(".memo-window")?.remove();

  const win=document.createElement("div");
  win.className="memo-window";

  const header=document.createElement("div");
  header.className="memo-window-header";

  const title=document.createElement("div");
  title.className="memo-window-title";
  title.textContent="メモ"+(index+1);

  const close=document.createElement("button");
  close.type="button";
  close.className="memo-window-close";
  close.textContent="×";

  header.append(title,close);

  const body=document.createElement("div");
  body.className="memo-window-body";

  const textarea=document.createElement("textarea");
  textarea.value=current;
  textarea.placeholder="メモ"+(index+1);

  textarea.oninput=()=>{
    editingMemos[index]=textarea.value;

    const button=document.querySelector(
      '.memo-editor-button[data-memo-index="'+index+'"]'
    );

    if(button){
      button.textContent=
        textarea.value.trim()
          ? textarea.value.split("\n")[0]
          : "メモ"+(index+1);
    }

    saveDraft();
  };

  body.appendChild(textarea);
  win.append(header,body);
  document.body.appendChild(win);

  close.onclick=()=>{
    editingMemos[index]=textarea.value;
    saveDraft();
    win.remove();
  };

  /* =========================
     ヘッダーをドラッグした時だけ移動
  ========================= */

  let dragging=false;
  let offsetX=0;
  let offsetY=0;

  const move=e=>{
    if(!dragging)return;

    win.style.left=(e.clientX-offsetX)+"px";
    win.style.top=(e.clientY-offsetY)+"px";
  };

  const stop=()=>{
    if(!dragging)return;

    dragging=false;

    document.removeEventListener("mousemove",move);
    document.removeEventListener("mouseup",stop);
  };

  header.addEventListener("mousedown",e=>{
    /* ×ボタンを押した場合はドラッグ開始しない */
    if(e.target===close)return;

    const rect=win.getBoundingClientRect();

    offsetX=e.clientX-rect.left;
    offsetY=e.clientY-rect.top;

    win.style.transform="none";

    dragging=true;

    document.addEventListener("mousemove",move);
    document.addEventListener("mouseup",stop);

    e.preventDefault();
  });

  textarea.focus();
}

function renderEditor(title,date,text,fromDraft){
  setPhotoColumns(editingPhotoColumns);

  document.getElementById("main").innerHTML=`
  <div class="panel">
    ${editingId!==null?`
<div class="editor-heading">
  <span class="editor-heading-title">記録を編集</span>
</div>
`:""}
    ${fromDraft?'<div class="draft-status" style="display:block">前回の入力内容を下書きから復元</div>':""}

    <input id="tripTitle" class="title-input" placeholder="題名"
      value="${escapeAttr(title)}">

    <input id="tripDate" type="hidden" value="${escapeAttr(date)}">

        <div class="travel-info-row" style="display:grid;grid-template-columns:1fr 1.5fr 1fr;gap:10px;margin-bottom:12px;">
          <label>
            都道府県
            <select id="tripPrefecture" style="width:100%;padding:10px;border:1px solid var(--border);border-radius:8px;background:var(--inputBg);color:var(--inputText);">
              <option value="">選択してください</option>
              ${["北海道","青森県","岩手県","宮城県","秋田県","山形県","福島県","茨城県","栃木県","群馬県","埼玉県","千葉県","東京都","神奈川県","新潟県","富山県","石川県","福井県","山梨県","長野県","岐阜県","静岡県","愛知県","三重県","滋賀県","京都府","大阪府","兵庫県","奈良県","和歌山県","鳥取県","島根県","岡山県","広島県","山口県","徳島県","香川県","愛媛県","高知県","福岡県","佐賀県","長崎県","熊本県","大分県","宮崎県","鹿児島県","沖縄県"].map(p=>`<option value="${p}">${p}</option>`).join("")}
            </select>
          </label>
          <label>
            最寄駅
            <input id="tripStation" type="text" placeholder="最寄駅" style="margin:0;">
          </label>
          <label>
            移動時間目安(分)
            <input id="tripTravelMinutes" type="number" min="0" placeholder="分" style="margin:0;">
          </label>
        </div>

        ${[1,2,3].map(n=>`
          <div style="display:grid;grid-template-columns:minmax(160px,1.4fr) minmax(0,2fr);gap:10px;align-items:center;margin-bottom:12px;">
            <input id="tripPurpose${n}" type="text" placeholder="目的${n}" style="margin:0;">
            <div style="display:grid;grid-template-columns:minmax(0,1fr) 24px minmax(0,1fr);gap:5px;align-items:center;">
              <div style="display:flex;gap:4px;min-width:0;">
                <select id="tripStartMonth${n}" style="width:55%;min-width:0;padding:9px 2px;border:1px solid var(--border);border-radius:7px;background:var(--inputBg);color:var(--inputText);">
                  <option value="">月</option>
                  ${Array.from({length:12},(_,i)=>`<option value="${String(i+1).padStart(2,"0")}">${String(i+1).padStart(2,"0")}月</option>`).join("")}
                </select>
                <select id="tripStartPeriod${n}" style="width:45%;min-width:0;padding:9px 2px;border:1px solid var(--border);border-radius:7px;background:var(--inputBg);color:var(--inputText);">
                  <option value="">旬</option>
                  <option value="上旬">上旬</option>
                  <option value="中旬">中旬</option>
                  <option value="下旬">下旬</option>
                </select>
              </div>
              <span style="text-align:center;">~</span>
              <div style="display:flex;gap:4px;min-width:0;">
                <select id="tripEndMonth${n}" style="width:55%;min-width:0;padding:9px 2px;border:1px solid var(--border);border-radius:7px;background:var(--inputBg);color:var(--inputText);">
                  <option value="">月</option>
                  ${Array.from({length:12},(_,i)=>`<option value="${String(i+1).padStart(2,"0")}">${String(i+1).padStart(2,"0")}月</option>`).join("")}
                </select>
                <select id="tripEndPeriod${n}" style="width:45%;min-width:0;padding:9px 2px;border:1px solid var(--border);border-radius:7px;background:var(--inputBg);color:var(--inputText);">
                  <option value="">旬</option>
                  <option value="上旬">上旬</option>
                  <option value="中旬">中旬</option>
                  <option value="下旬">下旬</option>
                </select>
              </div>
            </div>
          </div>
        `).join("")}

    <textarea id="tripText" placeholder="本文">${escapeHtml(text)}</textarea>

    <h3>メモ</h3>

<div id="memoEditor" class="memo-editor">
  <div class="memo-editor-row">
    <button type="button" class="memo-editor-button" data-memo-index="0"></button>
    <button type="button" class="memo-editor-button" data-memo-index="1"></button>
    <button type="button" class="memo-editor-button" data-memo-index="2"></button>
    <button type="button" class="memo-editor-button" data-memo-index="3"></button>
  </div>

  <div class="memo-editor-row">
    <button type="button" class="memo-editor-button" data-memo-index="4"></button>
    <button type="button" class="memo-editor-button" data-memo-index="5"></button>
    <button type="button" class="memo-editor-button" data-memo-index="6"></button>
    <button type="button" class="memo-editor-button" data-memo-index="7"></button>
  </div>
</div>

    <h3>写真</h3>

    <div class="photo-count-editor">
      <div class="photo-count-editor-title">
        この記録の写真欄サイズ(1行あたりの列数)
      </div>
      <div id="editorPhotoCount" class="photo-count-editor-buttons"></div>
    </div>

    <div id="photoDrop" class="drop-zone">
      写真をここへドラッグ&ドロップ<br>
      またはクリックして写真を選択
      <input id="photoInput" type="file" accept="image/*" multiple hidden>
    </div>

    <div id="editPhotos" class="edit-photo-grid"></div>
    <div id="saveStatus" class="save-status"></div>

<button id="saveTripBtn" class="action edit-action save">保存</button>
<button id="cancelTripBtn" class="action edit-action cancel">キャンセル</button>
  </div>`;

document.querySelectorAll(".memo-editor-button").forEach(button=>{
  const index=Number(button.dataset.memoIndex);
  const value=String(editingMemos[index]||"").split("\n")[0];

  button.textContent=
    value.trim()
      ? value
      : "メモ"+(index+1);

  button.onclick=()=>{
    openMemoEditor(index);
  };
});

  renderEditorPhotoCount();
  setupPhotoDrop();
  renderEditPhotos();

  document.getElementById("saveTripBtn").onclick=saveTripOnce;

  document.getElementById("cancelTripBtn").onclick=()=>{
    clearDraft();
    editingId=null;
    editingPhotos=[];
    editingPhotoColumns=DEFAULT_PHOTO_COLUMNS;
    showYears();
  };

  const ti=document.getElementById("tripTitle");
  const di=document.getElementById("tripDate");
  const tx=document.getElementById("tripText");

  ti.oninput=saveDraft;

  di.oninput=()=>{
    updateWeekday(di.value);
    saveDraft();
  };

  tx.oninput=saveDraft;

  updateWeekday(date);
}

/* =========================
   列数設定
========================= */
function renderEditorPhotoCount(){
  const box=document.getElementById("editorPhotoCount");
  if(!box)return;
  box.innerHTML="";

  PHOTO_COUNT_OPTIONS.forEach(n=>{
    const b=document.createElement("button");
    b.type="button";
    b.className="photo-count-editor-button";
    b.textContent=n+"列";
    if(normalizePhotoColumns(editingPhotoColumns)===n)
      b.classList.add("selected");

    b.onclick=()=>{
      editingPhotoColumns=normalizePhotoColumns(n);
      setPhotoColumns(editingPhotoColumns);
      renderEditorPhotoCount();
      renderEditPhotos();
      saveDraft();
    };
    box.appendChild(b);
  });
}

/* =========================
   保存
========================= */
async function saveTripOnce(){
  if(saveLock)return;
  saveLock=true;

  const button=document.getElementById("saveTripBtn");
  if(button)button.disabled=true;

  try{
    const getValue=id=>
      document.getElementById(id)?.value||"";

    const title=getValue("tripTitle").trim();
    const date=getValue("tripDate");
    const text=getValue("tripText");

    if(!title){
      alert("題名を入力してください。");
      return;
    }

    const prefecture=getValue("tripPrefecture");
    const station=getValue("tripStation").trim();
    const travelMinutes=getValue("tripTravelMinutes");

    const purposes=[1,2,3].map(n=>({
      purpose:getValue("tripPurpose"+n).trim(),
      startMonth:getValue("tripStartMonth"+n),
      startPeriod:getValue("tripStartPeriod"+n),
      endMonth:getValue("tripEndMonth"+n),
      endPeriod:getValue("tripEndPeriod"+n)
    }));

    const photos=editingPhotos.map(p=>({
      id:p.id,
      imageId:p.imageId,
      comment:p.comment||""
    }));

    const photoColumns=normalizePhotoColumns(editingPhotoColumns);

    const memos=Array.isArray(editingMemos)
      ? [...editingMemos,"","","","","","",""].slice(0,8)
      : ["","","","","","","",""];

    let targetTrip,newTrip=false;

    if(editingId===null){
      targetTrip={
        id:Date.now()+"_"+Math.random().toString(36).slice(2),
        title,
        date,
        text,
        prefecture,
        station,
        travelMinutes,
        purposes,
        memos,
        photoColumns,
        photos
      };

      trips.push(targetTrip);
      newTrip=true;

    }else{
      targetTrip=trips.find(
        x=>String(x.id)===String(editingId)
      );

      if(!targetTrip){
        editingId=null;

        targetTrip={
          id:Date.now()+"_"+Math.random().toString(36).slice(2),
          title,
          date,
          text,
          prefecture,
          station,
          travelMinutes,
          purposes,
          memos,
          photoColumns,
          photos
        };

        trips.push(targetTrip);
        newTrip=true;

      }else{
        targetTrip.title=title;
        targetTrip.date=date;
        targetTrip.text=text;
        targetTrip.prefecture=prefecture;
        targetTrip.station=station;
        targetTrip.travelMinutes=travelMinutes;
        targetTrip.purposes=purposes;
        targetTrip.memos=memos;
        targetTrip.photoColumns=photoColumns;
        targetTrip.photos=photos;
      }
    }

    if(!(await saveTrips())){
      if(newTrip){
        trips=trips.filter(x=>x.id!==targetTrip.id);
      }
      return;
    }

    await clearDraft();
    editingId=targetTrip.id;

    showYears();
    if(date)showYear(date.slice(0,4));

  }catch(e){
    console.error(e);
    alert(
      "保存中にエラーが発生しました。\n"+
      "保存処理を完了できませんでした。"
    );

  }finally{
    saveLock=false;
    if(button)button.disabled=false;
  }
}

/* =========================
   写真追加
   ★枚数制限を撤廃
========================= */
function setupPhotoDrop(){
  const zone=document.getElementById("photoDrop");
  const input=document.getElementById("photoInput");
  if(!zone||!input)return;

  zone.onclick=e=>{
    if(e.target!==input)input.click();
  };

  input.onchange=e=>{
    addPhotoFiles([...e.target.files]);
    input.value="";
  };

  zone.ondragover=e=>{
    e.preventDefault();
    zone.classList.add("dragover");
  };

  zone.ondragleave=()=>zone.classList.remove("dragover");

  zone.ondrop=e=>{
    e.preventDefault();
    zone.classList.remove("dragover");
    addPhotoFiles([...e.dataTransfer.files].filter(f=>f.type.startsWith("image/")));
  };
}

async function addPhotoFiles(files){
  if(!files.length)return;

  const status=document.getElementById("saveStatus");
  if(status){
    status.style.display="block";
    status.textContent="写真を縮小・保存しています…";
  }

  try{
    let added=0;

    for(const file of files){
      const blob=await compressImageBlob(file,1600,1600,.75);

const imageId="img_"+Date.now()+"_"+Math.random().toString(36).slice(2);
await savePhotoToFolder(imageId,blob);

      editingPhotos.push({
        id:"photo_"+Date.now()+"_"+Math.random().toString(36).slice(2),
        imageId,
        comment:""
      });

      added++;
      await renderEditPhotos();
      saveDraft();
    }

    if(status)status.textContent=added+"枚の写真を追加しました。";
  }catch(e){
    console.error(e);
    alert("写真を追加できませんでした。\nブラウザの画像保存領域を確認してください。");
  }finally{
    setTimeout(()=>{
      if(status)status.style.display="none";
    },1500);
  }
}

/* =========================
   画像圧縮
========================= */
function compressImageBlob(file,maxWidth,maxHeight,quality){
  return new Promise((resolve,reject)=>{
    const reader=new FileReader();
    reader.onerror=()=>reject(new Error("画像読み込み失敗"));
    reader.onload=()=>{
      const img=new Image();
      img.onerror=()=>reject(new Error("画像読み込み失敗"));
      img.onload=()=>{
        let w=img.width,h=img.height;
        const ratio=Math.min(maxWidth/w,maxHeight/h,1);
        w=Math.max(1,Math.round(w*ratio));
        h=Math.max(1,Math.round(h*ratio));

        const canvas=document.createElement("canvas");
        canvas.width=w;canvas.height=h;
        const ctx=canvas.getContext("2d");
        ctx.fillStyle="#fff";
        ctx.fillRect(0,0,w,h);
        ctx.drawImage(img,0,0,w,h);

        canvas.toBlob(blob=>{
          if(!blob)return reject(new Error("画像圧縮失敗"));
          resolve(blob);
        },"image/jpeg",quality);
      };
      img.src=reader.result;
    };
    reader.readAsDataURL(file);
  });
}

/* =========================
   編集写真
========================= */
async function renderEditPhotos(){
  setPhotoColumns(editingPhotoColumns);
  const box=document.getElementById("editPhotos");
  if(!box)return;
  box.innerHTML="";

  for(let index=0;index<editingPhotos.length;index++){
    const photo=editingPhotos[index];

    const card=document.createElement("div");
    card.className="edit-photo";
    card.draggable=true;
    card.dataset.index=index;

    const frame=document.createElement("div");
    frame.className="edit-photo-frame";

    const img=document.createElement("img");

    try{
      const blob=await loadPhotoFromFolder(photo.imageId);
      if(blob)img.src=URL.createObjectURL(blob);
    }catch(e){
      console.error(e);
    }

    frame.appendChild(img);
    card.appendChild(frame);

    const cb=document.createElement("div");
    cb.className="edit-comment-box";

    const comment=document.createElement("textarea");
    comment.placeholder="コメント";
    comment.value=photo.comment||"";

    comment.oninput=()=>{
      photo.comment=comment.value;
      saveDraft();
    };

    cb.appendChild(comment);
    card.appendChild(cb);

const del=document.createElement("button");
del.className="action edit-action delete";
del.textContent="削除";

    del.onclick=async()=>{
      if(!photo.imageId)return;

      try{
        await deletePhotoFromFolder(photo.imageId);

        editingPhotos.splice(index,1);

        saveDraft();
        await renderEditPhotos();

      }catch(e){
        console.error("写真削除エラー:",e);
        alert(
          "写真ファイルを削除できなかったため、\n"+
          "写真の削除処理を中止しました。"
        );
      }
    };

    card.appendChild(del);

    setupEditDrag(card);
    box.appendChild(card);
  }
}

function setupEditDrag(card){
  card.ondragstart=e=>{
    card.classList.add("dragging");
    e.dataTransfer.setData("text/plain",card.dataset.index);
  };

  card.ondragend=()=>{
    card.classList.remove("dragging");
    document.querySelectorAll(".drag-over").forEach(x=>x.classList.remove("drag-over"));
  };

  card.ondragover=e=>{
    e.preventDefault();
    if(!card.classList.contains("dragging"))card.classList.add("drag-over");
  };

  card.ondragleave=()=>card.classList.remove("drag-over");

  card.ondrop=e=>{
    e.preventDefault();
    const from=Number(e.dataTransfer.getData("text/plain"));
    const to=Number(card.dataset.index);
    card.classList.remove("drag-over");
    movePhoto(editingPhotos,from,to);
    saveDraft();
    renderEditPhotos();
  };
}

function movePhoto(array,from,to){
  if(from<0||to<0||from>=array.length||to>=array.length||from===to)return;
  const item=array.splice(from,1)[0];
  array.splice(to,0,item);
}

/* =========================
   詳細表示
========================= */
async function showDetail(id){
  const trip=trips.find(x=>x.id===id);
  if(!trip)return;

  setPhotoColumns(trip.photoColumns);
  setActive("years");

  const main=document.getElementById("main");
  main.innerHTML="";

  const box=document.createElement("div");
  box.className="detail";

  const title=document.createElement("div");
  title.className="detail-title";
  title.textContent=trip.title;

  const date=document.createElement("div");
  date.className="detail-date";
  date.textContent=dateText(trip.date);

  box.append(title,date);

  if(trip.text){
    const text=document.createElement("div");
    text.className="detail-text";
    text.textContent=trip.text;
    box.appendChild(text);
  }

  /* =========================
     メモ表示
  ========================= */
  const memoTitle=document.createElement("h3");
  memoTitle.textContent="メモ";
  box.appendChild(memoTitle);

  const memoGrid=document.createElement("div");
  memoGrid.className="memo-view-grid";

  const memos=Array.isArray(trip.memos)
    ? trip.memos
    : [];

  for(let i=0;i<8;i++){
    const item=document.createElement("div");
    item.className="memo-view-item";

    const value=String(memos[i]||"").split("\n")[0];

    if(!value){
  item.textContent="未入力";
}else{
  item.textContent=value;

  requestAnimationFrame(()=>{
    if(item.scrollWidth>item.clientWidth){
      let text=value;

      while(text.length>0){
        item.textContent=text+"…";

        if(item.scrollWidth<=item.clientWidth){
          break;
        }

        text=text.slice(0,-1);
      }
    }
  });
}

    memoGrid.appendChild(item);
  }

  box.appendChild(memoGrid);

  const grid=document.createElement("div");
  grid.className="photo-grid";
  box.appendChild(grid);

  await renderViewPhotos(trip,grid);

  const edit=document.createElement("button");
  edit.className="action save";
  edit.textContent="編集";
  edit.onclick=()=>editTrip(trip.id);

  const del=document.createElement("button");
  del.className="action delete";
  del.textContent="削除";

  del.onclick=async()=>{
    if(!confirm("この記録を削除しますか?"))return;

    const old=trips;

    trips=trips.filter(x=>x.id!==trip.id);

    if(await saveTrips()){

      for(const p of trip.photos||[]){
        try{
          await deletePhotoFromFolder(p.imageId);
        }catch(e){
          console.error("写真削除エラー:",e);
        }
      }

      showYears();

    }else{
      trips=old;
    }
  };

  const back=document.createElement("button");
  back.className="action cancel";
  back.textContent="戻る";
  back.onclick=()=>showYear(trip.date.slice(0,4));

  box.append(edit,del,back);
  main.appendChild(box);
}

async function renderViewPhotos(trip,grid){
  setPhotoColumns(trip.photoColumns);
  grid.innerHTML="";

  for(let index=0;index<(trip.photos||[]).length;index++){
    const photo=trip.photos[index];

    const item=document.createElement("div");
    item.className="photo-item";
    item.draggable=true;
    item.dataset.index=index;

    const frame=document.createElement("div");
    frame.className="photo-frame";

    const img=document.createElement("img");

    try{
      const blob=await loadPhotoFromFolder(photo.imageId);

      if(blob){
        const url=URL.createObjectURL(blob);
        img.src=url;
        img.onclick=()=>openPhoto(url);
      }
    }catch(e){console.error(e)}

    frame.appendChild(img);
    item.appendChild(frame);

    if(photo.comment){
      const cb=document.createElement("div");
      cb.className="photo-comment-box";
      const c=document.createElement("div");
      c.className="photo-comment";
      c.textContent=photo.comment;
      cb.appendChild(c);
      item.appendChild(cb);
    }

    item.ondragstart=e=>{
      item.classList.add("dragging");
      e.dataTransfer.setData("text/plain",index);
    };

    item.ondragend=()=>item.classList.remove("dragging");

    item.ondragover=e=>{
      e.preventDefault();
      if(!item.classList.contains("dragging"))item.classList.add("drag-over");
    };

    item.ondragleave=()=>item.classList.remove("drag-over");

item.ondrop=async e=>{
  e.preventDefault();
  const from=Number(e.dataTransfer.getData("text/plain"));
  item.classList.remove("drag-over");
  movePhoto(trip.photos,from,index);
  if(await saveTrips())renderViewPhotos(trip,grid);
};

    grid.appendChild(item);
  }
}

/* =========================
   編集
========================= */
async function editTrip(id){
  const trip=trips.find(x=>String(x.id)===String(id));
  if(!trip)return;

  editingId=trip.id;
  editingPhotos=JSON.parse(JSON.stringify(trip.photos||[]));
  editingPhotoColumns=normalizePhotoColumns(trip.photoColumns);

  editingMemos=Array.isArray(trip.memos)
    ? [...trip.memos,"","","","","","",""].slice(0,8)
    : ["","","","","","","",""];

  renderEditor(trip.title||"",trip.date||"",trip.text||"",false);

  const setValue=(id,value)=>{
    const el=document.getElementById(id);
    if(el)el.value=value??"";
  };

  setValue("tripPrefecture",trip.prefecture);
  setValue("tripStation",trip.station);
  setValue("tripTravelMinutes",trip.travelMinutes);

  const purposes=Array.isArray(trip.purposes)?trip.purposes:[];

  [1,2,3].forEach(n=>{
    const p=purposes[n-1]||{};

    setValue("tripPurpose"+n,p.purpose);
    setValue("tripStartMonth"+n,p.startMonth);
    setValue("tripStartPeriod"+n,p.startPeriod);
    setValue("tripEndMonth"+n,p.endMonth);
    setValue("tripEndPeriod"+n,p.endPeriod);
  });

  [
    "tripPrefecture",
    "tripStation",
    "tripTravelMinutes",
    ...[1,2,3].flatMap(n=>[
      "tripPurpose"+n,
      "tripStartMonth"+n,
      "tripStartPeriod"+n,
      "tripEndMonth"+n,
      "tripEndPeriod"+n
    ])
  ].forEach(id=>{
    const el=document.getElementById(id);
    if(el){
      el.addEventListener("input",saveDraft);
      el.addEventListener("change",saveDraft);
    }
  });

  saveDraftBeforeNavigation();
}

/* =========================
   設定
========================= */
function showSettings(){
  saveDraftBeforeNavigation();
  setActive("settings");
  document.getElementById("main").innerHTML=`
  <div class="settings-section">
    <h2>タイトル変更</h2>
    <input id="newAppTitle" value="${escapeAttr(settings.title)}">
    <button id="titleChangeBtn" class="action save">タイトル変更</button>
  </div>
  <div class="settings-section">
    <h2>イメージカラー</h2>
    <p>色をドラッグして並べ替えできます。</p>
    <div id="colors" class="color-list"></div>
  </div>
  <div class="settings-section">
    <h2>ダークモード</h2>
    <div id="darkMode" class="dark-mode-row"></div>
  </div>
  <div class="settings-section">
  <h2>文字設定</h2>
  <div id="sizes"></div>
  <button id="resetTextSettingsBtn" class="action cancel">
    文字設定リセット
  </button>
</div>`;

  document.getElementById("titleChangeBtn").onclick=async()=>{
    const v=document.getElementById("newAppTitle").value.trim();
    if(!v)return;

    const old=settings.title;
    settings.title=v;

    if(!(await saveSettings())){
      settings.title=old;
      return;
    }

    applySettings();
    showSettings();
  };

  renderColors();
renderDarkMode();
renderSizes();

document.getElementById("resetTextSettingsBtn").onclick=
  resetTextSettings;
}

/* =========================
   イメージカラー
========================= */
function renderColors(){
  const box=document.getElementById("colors");
  if(!box)return;
  box.innerHTML="";

  settings.colors.forEach((color,index)=>{
    const b=document.createElement("button");
    b.className="color-choice";

    if(color.toLowerCase()==="#ffffff"){
      b.classList.add("white-color");
    }

    b.style.background=color;
    b.draggable=true;

    if(settings.colorIndex===index)b.classList.add("selected");

    b.onclick=()=>{
      settings.colorIndex=index;
      saveSettings();
      applySettings();
      renderColors();
    };

    b.ondragstart=e=>e.dataTransfer.setData("text/plain",index);

    b.ondragover=e=>{
      e.preventDefault();
      b.classList.add("drag-over");
    };

    b.ondragleave=()=>b.classList.remove("drag-over");

    b.ondrop=e=>{
      e.preventDefault();
      const from=Number(e.dataTransfer.getData("text/plain"));
      const to=index;
      b.classList.remove("drag-over");
      if(from===to)return;

      const selected=settings.colors[settings.colorIndex];
      const moved=settings.colors.splice(from,1)[0];
      settings.colors.splice(to,0,moved);
      settings.colorIndex=settings.colors.indexOf(selected);

      saveSettings();
      applySettings();
      renderColors();
    };

    box.appendChild(b);
  });
}

/* =========================
   ダークモード
========================= */
function renderDarkMode(){
  const box=document.getElementById("darkMode");
  if(!box)return;

  box.innerHTML="";

  [
    [false,"OFF"],
    [true,"ON"]
  ].forEach(([value,label])=>{
    const b=document.createElement("button");
    b.className="dark-mode-button";
    b.textContent=label;

    if(settings.darkMode===value)
      b.classList.add("selected");

    b.onclick=()=>{
      const old=settings.darkMode;
      settings.darkMode=value;

      if(!saveSettings()){
        settings.darkMode=old;
        return;
      }

      applySettings();
      renderDarkMode();
    };

    box.appendChild(b);
  });
}


/* =========================
   文字設定
========================= */
const SIZE_TARGETS=[
  ["見出し","titleSize","titleFont"],
  ["一覧","dateSize","dateFont"],
  ["編集","editSize","editFont"],
  ["本文","bodySize","bodyFont"],
  ["メモ","commentSize","commentFont"],
  ["設定","settingSize","settingFont"]
];

function renderSizes(){
  const box=document.getElementById("sizes");
  if(!box)return;

  box.innerHTML="";

  const descriptions={
    "見出し":"サイドバーとアプリ上部タイトル",
    "一覧":"一覧画面の文字",
    "編集":"編集画面の文字",
    "本文":"編集画面の本文",
    "メモ":"編集画面のメモ・写真コメント",
    "設定":"設定・背景画像画面の文字"
  };

  SIZE_TARGETS.forEach(([label,sizeKey,fontKey])=>{

    const row=document.createElement("div");
    row.className="size-setting";

    const inner=document.createElement("div");
    inner.className="size-setting-row";

    /* 項目名 */
    const name=document.createElement("div");
    name.className="size-setting-name";
    name.textContent=label;

    inner.appendChild(name);


    /* =========================
       サイズ 1〜5
    ========================= */
    const buttons=document.createElement("div");
    buttons.className="size-buttons";

    [1,2,3,4,5].forEach(n=>{
      const b=document.createElement("button");

      b.className="size-button";
      b.textContent=n;

      if(settings[sizeKey]===n)
        b.classList.add("selected");

      b.onclick=()=>{
        const old=settings[sizeKey];

        settings[sizeKey]=n;

        if(!saveSettings()){
          settings[sizeKey]=old;
          return;
        }

        applySettings();
        renderSizes();
      };

      buttons.appendChild(b);
    });

    inner.appendChild(buttons);


    /* =========================
       説明文
    ========================= */
    const description=document.createElement("span");

    description.textContent=
      descriptions[label]||"";

    description.style.fontWeight="normal";
    description.style.color="#777";
    description.style.fontSize="var(--settingSize)";
    description.style.marginLeft="8px";
    description.style.whiteSpace="nowrap";

    inner.appendChild(description);


    /* =========================
       フォント選択
    ========================= */
    const fontBox=document.createElement("div");
    fontBox.className="font-select";

    FONTS.forEach((font,index)=>{

      const b=document.createElement("button");

      b.className="font-choice";
      b.style.fontFamily=font[1];

      if(settings[fontKey]===font[0])
        b.classList.add("selected");


      /*
        フォント名は表示せず、
        A あ
        B あ
        C あ
        の形式で表示
      */
      const patternName=
        String.fromCharCode(65+index);

      b.innerHTML=
        `<span class="font-choice-name">${patternName} </span>`+
        `<span class="font-choice-sample" style="font-family:${font[1]}">あ</span>`;


      b.onclick=()=>{
        const old=settings[fontKey];

        settings[fontKey]=font[0];

        if(!saveSettings()){
          settings[fontKey]=old;
          return;
        }

        applySettings();
        renderSizes();
      };

      fontBox.appendChild(b);
    });

    inner.appendChild(fontBox);

    row.appendChild(inner);
    box.appendChild(row);
  });
}


/* =========================
   文字設定リセット
========================= */
async function resetTextSettings(){

  const ok=confirm(
    "文字設定をリセットします。\n\n"+
    "文字サイズをすべて「2」に戻し、"+
    "フォントをすべて「游ゴシック」にします。\n\n"+
    "よろしいですか?"
  );

  if(!ok)return;


  const oldValues={
    titleSize:settings.titleSize,
    dateSize:settings.dateSize,
    editSize:settings.editSize,
    bodySize:settings.bodySize,
    commentSize:settings.commentSize,
    listSize:settings.listSize,
    settingSize:settings.settingSize,

    titleFont:settings.titleFont,
    dateFont:settings.dateFont,
    editFont:settings.editFont,
    bodyFont:settings.bodyFont,
    commentFont:settings.commentFont,
    listFont:settings.listFont,
    settingFont:settings.settingFont
  };


  settings.titleSize=2;
  settings.dateSize=2;
  settings.editSize=2;
  settings.bodySize=2;
  settings.commentSize=2;
  settings.listSize=2;
  settings.settingSize=2;

  settings.titleFont="游ゴシック";
  settings.dateFont="游ゴシック";
  settings.editFont="游ゴシック";
  settings.bodyFont="游ゴシック";
  settings.commentFont="游ゴシック";
  settings.listFont="游ゴシック";
  settings.settingFont="游ゴシック";


  if(!(await saveSettings())){
    Object.assign(settings,oldValues);
    return;
  }

  applySettings();
  renderSizes();
}


/* =========================
   白枠透過
========================= */
function renderTransparency(){
  const box=document.getElementById("transparency");
  if(!box)return;

  box.innerHTML="";

  [1,2,3,4,5].forEach(n=>{

    const b=document.createElement("button");

    b.className="transparency-button";
    b.textContent=n;

    if(settings.panelTransparency===n)
      b.classList.add("selected");

    b.onclick=()=>{
      const old=settings.panelTransparency;

      settings.panelTransparency=n;

      if(!saveSettings()){
        settings.panelTransparency=old;
        return;
      }

      applySettings();
      renderTransparency();
    };

    box.appendChild(b);
  });
}

function renderTransparency(){
  const box=document.getElementById("transparency");
  if(!box)return;
  box.innerHTML="";

  [1,2,3,4,5].forEach(n=>{
    const b=document.createElement("button");
    b.className="transparency-button";
    b.textContent=n;
    if(settings.panelTransparency===n)b.classList.add("selected");

    b.onclick=()=>{
      const old=settings.panelTransparency;
      settings.panelTransparency=n;
      if(!saveSettings()){settings.panelTransparency=old;return}
      applySettings();
      renderTransparency();
    };

    box.appendChild(b);
  });
}

/* =========================
   背景画像・背景カラー
========================= */
async function showBackground(){
  saveDraftBeforeNavigation();
  setActive("background");

  let preview="";
  if(backgroundObjectUrl)
    preview=`<img class="background-preview" src="${backgroundObjectUrl}">`;

  document.getElementById("main").innerHTML=`
<div class="settings-section">
  <h2>背景画像</h2>
  <p>背景画像を設定すると、記録画面の後ろに表示されます。</p>
  <div id="backgroundDrop" class="drop-zone">
    背景画像をここへドラッグ&ドロップ<br>
    またはクリックして選択
    <input id="backgroundInput" type="file" accept="image/*" hidden>
    ${preview}
  </div>
  <button id="clearBackground" class="action delete">
    背景画像を削除
  </button>
</div>
<div class="settings-section">
  <h2>背景画像使用時の白枠透過</h2>
  <div id="transparency" class="transparency-row"></div>
</div>

  <div class="settings-section">
    <h2>背景カラー</h2>
    <p>背景画像を設定していないときに適用されます。色をドラッグして並べ替えできます。</p>
    <div id="backgroundColors" class="color-list"></div>
  </div>`;

  renderBackgroundColors();
renderTransparency();

  const zone=document.getElementById("backgroundDrop");
  const input=document.getElementById("backgroundInput");

  zone.onclick=e=>{
    if(e.target.tagName!=="IMG")input.click();
  };

  input.onchange=e=>{
    if(e.target.files[0])setBackground(e.target.files[0]);
  };

  zone.ondragover=e=>{
    e.preventDefault();
    zone.classList.add("dragover");
  };

  zone.ondragleave=()=>zone.classList.remove("dragover");

  zone.ondrop=e=>{
    e.preventDefault();
    zone.classList.remove("dragover");
    const file=[...e.dataTransfer.files].find(f=>f.type.startsWith("image/"));
    if(file)setBackground(file);
  };

  document.getElementById("clearBackground").onclick=clearBackground;
}

function renderBackgroundColors(){
  const box=document.getElementById("backgroundColors");
  if(!box)return;

  box.innerHTML="";

  settings.backgroundColors.forEach((color,index)=>{
    const b=document.createElement("button");
    b.className="color-choice";

    if(color.toLowerCase()==="#ffffff"){
      b.classList.add("white-color");
    }

    b.style.background=color;
    b.draggable=true;

    if(settings.backgroundColorIndex===index)
      b.classList.add("selected");

    b.onclick=()=>{
      settings.backgroundColorIndex=index;

      if(!saveSettings())return;

      applyBackground();
      renderBackgroundColors();
    };

    b.ondragstart=e=>{
      e.dataTransfer.setData("text/plain",index);
    };

    b.ondragover=e=>{
      e.preventDefault();
      b.classList.add("drag-over");
    };

    b.ondragleave=()=>{
      b.classList.remove("drag-over");
    };

    b.ondrop=e=>{
      e.preventDefault();

      const from=Number(e.dataTransfer.getData("text/plain"));
      const to=index;

      b.classList.remove("drag-over");

      if(from===to)return;

      const selected=settings.backgroundColors[
        settings.backgroundColorIndex
      ];

      const moved=settings.backgroundColors.splice(from,1)[0];

      settings.backgroundColors.splice(to,0,moved);

      settings.backgroundColorIndex=
        settings.backgroundColors.indexOf(selected);

      saveSettings();
      applyBackground();
      renderBackgroundColors();
    };

    box.appendChild(b);
  });
}

async function setBackground(file){
  try{
    const blob=await compressImageBlob(file,1800,1200,.72);

    const newId=
      "background_"+Date.now()+"_"+
      Math.random().toString(36).slice(2);

    const folder=await recordFolderHandle.getDirectoryHandle(
      BACKGROUND_DIR,
      {create:true}
    );

    const fileHandle=await folder.getFileHandle(
      newId+".jpg",
      {create:true}
    );

    const writable=await fileHandle.createWritable();

    await writable.write(blob);
    await writable.close();

    const oldId=backgroundImageId;

    backgroundImageId=newId;
    settings.backgroundImageId=newId;

    if(!(await saveSettings())){
      backgroundImageId=oldId;
      settings.backgroundImageId=oldId;
      return;
    }

    await applyBackground();

    if(oldId){
      try{
        const oldFileHandle=await folder.getFileHandle(
          oldId+".jpg"
        );
        await oldFileHandle.remove();
      }catch(e){
        console.error(e);
      }
    }

    showBackground();

  }catch(e){
    console.error(e);
    alert("背景画像を保存できませんでした。");
  }
}

/* =========================
   背景画像削除
========================= */
async function clearBackground(){
  const old=backgroundImageId;

  try{
    backgroundImageId=null;
    settings.backgroundImageId=null;

    if(!(await saveSettings())){
      backgroundImageId=old;
      settings.backgroundImageId=old;
      return;
    }

    await applyBackground();

    if(old){
      try{
        const folder=await recordFolderHandle.getDirectoryHandle(
          BACKGROUND_DIR,
          {create:false}
        );

        const fileHandle=await folder.getFileHandle(
          old+".jpg"
        );

        await fileHandle.remove();
      }catch(e){
        console.error("背景画像ファイルの削除に失敗:",e);
      }
    }

    showBackground();

  }catch(e){
    console.error(e);

    backgroundImageId=old;
    settings.backgroundImageId=old;

    await applyBackground();

    alert("背景画像を削除できませんでした。");
  }
}

/* =========================
   背景
========================= */
async function applyBackground(){
  if(backgroundObjectUrl){
    URL.revokeObjectURL(backgroundObjectUrl);
    backgroundObjectUrl=null;
  }

  /*
    背景画像がある場合は
    記録管理フォルダから読み込む。
  */
  if(backgroundImageId){
    try{
      const folder=await recordFolderHandle.getDirectoryHandle(
        "background",
        {create:false}
      );

      const fileHandle=await folder.getFileHandle(
        backgroundImageId+".jpg"
      );

      const file=await fileHandle.getFile();

      backgroundObjectUrl=URL.createObjectURL(file);

      document.body.style.backgroundImage=
        `url("${backgroundObjectUrl}")`;

      return;

    }catch(e){
      console.error(e);
    }
  }

  /*
    背景画像がない場合だけ背景カラーを適用。
  */
  document.body.style.backgroundImage="";

  const backgroundColor=
    settings.backgroundColors[
      settings.backgroundColorIndex
    ]||"#ffffff";

  document.body.style.backgroundColor=backgroundColor;
}

/* =========================
   設定適用
========================= */
function applySettings(){
  const root=document.documentElement;

  const mainColor=settings.colors[settings.colorIndex]||DEFAULT_COLORS[0];
  root.style.setProperty("--main",mainColor);

  root.style.setProperty("--titleFont",getFontFamily(settings.titleFont));
  root.style.setProperty("--dateFont",getFontFamily(settings.dateFont));
  root.style.setProperty("--editFont",getFontFamily(settings.editFont));
  root.style.setProperty("--bodyFont",getFontFamily(settings.bodyFont));
  root.style.setProperty("--commentFont",getFontFamily(settings.commentFont));
  root.style.setProperty("--listFont",getFontFamily(settings.listFont));
  root.style.setProperty("--settingFont",getFontFamily(settings.settingFont));

const sizes={
  titleSize:[15,18,21,25,30],
  appTitleSize:[25,30,36,42,50],
  dateSize:[14,16,18,20,24],
  editSize:[12,16,20,24,28],
  bodySize:[12,14,16,18,20],
  commentSize:[12,14,16,18,20],
  listSize:[14,16,18,20,24],
  settingSize:[12,14,16,18,22]
};

const titleLevel=Math.max(
  0,
  Math.min(4,Number(settings.titleSize||2)-1)
);

root.style.setProperty(
  "--titleSize",
  sizes.titleSize[titleLevel]+"px"
);

root.style.setProperty(
  "--appTitleSize",
  sizes.appTitleSize[titleLevel]+"px"
);

[
  "dateSize",
  "editSize",
  "bodySize",
  "commentSize",
  "listSize",
  "settingSize"
].forEach(key=>{
  const n=Number(settings[key]||2);

  root.style.setProperty(
    "--"+key,
    sizes[key][Math.max(0,Math.min(4,n-1))]+"px"
  );
});

  document.body.classList.toggle("dark-mode",!!settings.darkMode);

  document.getElementById("appTitle").textContent=settings.title;

  /*
    背景カラーはダークモードでは変更しない。
  */
  applyBackground();
}

/* =========================
   写真拡大
========================= */
function openPhoto(src){
  const box=document.createElement("div");
  box.className="photo-window";

  const img=document.createElement("img");
  img.src=src;

  box.appendChild(img);
  box.onclick=()=>box.remove();

  document.body.appendChild(box);
}

/* =========================
   ユーティリティ
========================= */
function today(){
  const d=new Date();
  return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}`;
}

function getWeekday(value){
  if(!value)return "";
  const [y,m,d]=value.split("-").map(Number);
  const date=new Date(y,m-1,d);

  if(
    date.getFullYear()!==y||
    date.getMonth()!==m-1||
    date.getDate()!==d
  )return "";

  return ["日","月","火","水","木","金","土"][date.getDay()];
}

function updateWeekday(value){
  const box=document.getElementById("weekdayDisplay");
  if(!box)return;
  const w=getWeekday(value);
  box.textContent=w?`(${w})`:"";
}

function dateText(value){
  if(!value)return "";
  const [y,m,d]=value.split("-");
  const w=getWeekday(value);
  return w?`${y}年${m}月${d}日 (${w})`:`${y}年${m}月${d}日`;
}

function escapeHtml(value){
  return String(value??"")
    .replace(/&/g,"&amp;")
    .replace(/</g,"&lt;")
    .replace(/>/g,"&gt;");
}

function escapeAttr(value){
  return escapeHtml(value).replace(/"/g,"&quot;");
}

function getFontFamily(name){
  const f=FONTS.find(x=>x[0]===name);
  return f?f[1]:"Yu Gothic";
}

function blobToDataURL(blob){
  return new Promise((resolve,reject)=>{
    const r=new FileReader();
    r.onload=()=>resolve(r.result);
    r.onerror=reject;
    r.readAsDataURL(blob);
  });
}

async function dataURLToBlob(dataURL){
  return (await fetch(dataURL)).blob();
}
const permissionBackgrounds=[
  `
  radial-gradient(
    circle at 20% 20%,
    rgba(79,134,198,.25),
    transparent 35%
  ),
  radial-gradient(
    circle at 80% 80%,
    rgba(112,173,71,.12),
    transparent 35%
  ),
  linear-gradient(
    135deg,
    #171d25,
    #20262e 50%,
    #18211d
  )
  `,
  `
  radial-gradient(
    circle at 18% 22%,
    rgba(255,190,100,.38),
    transparent 35%
  ),
  radial-gradient(
    circle at 82% 25%,
    rgba(255,145,100,.30),
    transparent 35%
  ),
  radial-gradient(
    circle at 70% 85%,
    rgba(255,210,120,.28),
    transparent 40%
  ),
  linear-gradient(
    135deg,
    #fff5df 0%,
    #ffe9d6 50%,
    #fff7e8 100%
  )
  `,
  `
  radial-gradient(
    circle at 20% 20%,
    rgba(255,180,210,.42),
    transparent 35%
  ),
  radial-gradient(
    circle at 80% 30%,
    rgba(170,130,255,.38),
    transparent 36%
  ),
  radial-gradient(
    circle at 65% 85%,
    rgba(100,170,255,.28),
    transparent 40%
  ),
  linear-gradient(
    135deg,
    #fff0f5 0%,
    #eee9ff 50%,
    #e7f3ff 100%
  )
  `
];

function applyPermissionBackground(){
  const screen=document.querySelector(".permission-screen");

  if(!screen)return;

  screen.style.background=`
    radial-gradient(
      circle at 20% 20%,
      rgba(255,180,210,.42),
      transparent 35%
    ),
    radial-gradient(
      circle at 80% 30%,
      rgba(170,130,255,.38),
      transparent 36%
    ),
    radial-gradient(
      circle at 65% 85%,
      rgba(100,170,255,.28),
      transparent 40%
    ),
    linear-gradient(
      135deg,
      #fff0f5 0%,
      #eee9ff 50%,
      #e7f3ff 100%
    )
  `;
}

 /* =========================
    起動
 ========================= */
async function init(){

  /* =========================
     保存済みフォルダ取得
  ========================= */
  if(!recordFolderHandle){
    recordFolderHandle=await idbGet("__record_folder_handle__");
  }


  /* =========================
     フォルダ未選択
  ========================= */
  if(!recordFolderHandle){

  document.body.classList.add("permission-mode");

  document.getElementById("main").innerHTML=`

      <div class="permission-screen">

        <div class="permission-card">

          <div class="permission-icon">
            📁
          </div>

          <h2>記録管理フォルダ</h2>

          <p>
            最初に、作成済みの「記録管理」フォルダを指定してください。
          </p>

          <button id="selectRecordFolderBtn" class="action save">
            記録管理フォルダを選択
          </button>

        </div>

      </div>
    `;

    document.getElementById("selectRecordFolderBtn").onclick=async()=>{
      const ok=await selectRecordFolder();

      if(!ok)return;

      await init();
    };

    return;
  }


  /* =========================
     アクセス権限確認
  ========================= */
  const permission=await recordFolderHandle.queryPermission({
    mode:"readwrite"
  });


if(permission!=="granted"){

  document.body.classList.add("permission-mode");

  document.getElementById("main").innerHTML=`

      <div class="permission-screen">

        <div class="permission-card">

          <div class="permission-icon">
            📁
          </div>

          <h2>記録管理フォルダ</h2>

          <p>
            記録管理フォルダへのアクセス許可が必要です。
          </p>

          <button id="grantRecordFolderBtn" class="action save">
            記録管理フォルダへのアクセスを許可
          </button>

        </div>

      </div>
    `;

applyPermissionBackground();

    document.getElementById("grantRecordFolderBtn").onclick=async()=>{

      const permission=
        await recordFolderHandle.requestPermission({
          mode:"readwrite"
        });

      if(permission!=="granted"){
        alert(
          "記録管理フォルダへのアクセスを許可してください。"
        );
        return;
      }

      await init();
    };

    return;
  }


  /* =========================
     フォルダの実在確認
  ========================= */
  try{

    const fileHandle=
      await recordFolderHandle.getFileHandle(
        "records.json",
        {create:true}
      );

    await fileHandle.getFile();

  }catch(e){

    console.warn(
      "以前の記録管理フォルダが利用できません。",
      e
    );

    recordFolderHandle=null;

    document.getElementById("main").innerHTML=`
      <div class="permission-screen">

        <div class="permission-card">

          <div class="permission-icon">
            📁
          </div>

          <h2>記録管理フォルダ</h2>

          <p>
            以前の記録管理フォルダを確認できませんでした。<br>
            記録管理フォルダをもう一度選択してください。
          </p>

          <button id="selectRecordFolderBtn" class="action save">
            記録管理フォルダを選択
          </button>

        </div>

      </div>
    `;


    document.getElementById("selectRecordFolderBtn").onclick=async()=>{
      const ok=await selectRecordFolder();

      if(!ok)return;

      await init();
    };

    return;
  }


  /* =========================
     通常起動
  ========================= */
document.body.classList.remove("permission-mode");
  settings=await loadSettings();

  trips=await loadTrips();

  backgroundImageId=
    settings.backgroundImageId||null;

  applySettings();

  await applyBackground();

  setActive("years");

  showYears();
}


init();

</script>

</body>
</html>

95,272 chars · codepastes.com