{"id":26057,"date":"2025-06-12T09:48:22","date_gmt":"2025-06-12T00:48:22","guid":{"rendered":"http:\/\/www.tyosuke20xx.com\/blog\/?p=26057"},"modified":"2025-06-12T09:48:23","modified_gmt":"2025-06-12T00:48:23","slug":"%e4%ba%ba%e9%96%93%e3%81%ae%e8%a8%98%e6%86%b6%e3%82%92%e8%a8%98%e9%8c%b2%e3%81%99%e3%82%8b%e3%82%b5%e3%82%a4%e3%83%88","status":"publish","type":"post","link":"http:\/\/www.tyosuke20xx.com\/blog\/?p=26057","title":{"rendered":"\u4eba\u9593\u306e\u8a18\u61b6\u3092\u8a18\u9332\u3059\u308b\u30b5\u30a4\u30c8"},"content":{"rendered":"\n<pre class=\"wp-block-code\"><code>&lt;!DOCTYPE html>\n&lt;html lang=\"ja\">\n&lt;head>\n  &lt;meta charset=\"UTF-8\">\n  &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  &lt;title>Memory Recorder Pro&lt;\/title>\n  &lt;!-- Bootstrap CSS &amp; Icons -->\n  &lt;link rel=\"stylesheet\" href=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.3\/dist\/css\/bootstrap.min.css\" crossorigin=\"anonymous\">\n  &lt;link rel=\"stylesheet\" href=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap-icons@1.11.3\/font\/bootstrap-icons.css\">\n  &lt;!-- Chart.js for statistics -->\n  &lt;script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\">&lt;\/script>\n  &lt;style>\n    :root{\n      --bg-main:#ffffff;\n      --bg-gradient:linear-gradient(135deg,#f8f9fa 0%,#e9ecef 100%);\n      --text-main:#212529;\n      --accent:#0d6efd;\n    }\n    :root.dark{\n      --bg-main:#1e1e1e;\n      --bg-gradient:linear-gradient(135deg,#2b2b2b 0%,#1e1e1e 100%);\n      --text-main:#f8f9fa;\n    }\n    body{\n      background:var(--bg-gradient);\n      color:var(--text-main);\n      min-height:100vh;\n      display:flex;\n      align-items:center;\n      justify-content:center;\n      font-family:\"Helvetica Neue\",Helvetica,Arial,sans-serif;\n      transition:background .3s ease,color .3s ease;\n    }\n    .memory-app{\n      width:100%;\n      max-width:920px;\n      background:var(--bg-main);\n      padding:2rem 2.5rem;\n      border-radius:1.5rem;\n      box-shadow:0 4px 20px rgba(0,0,0,.1);\n      transition:background .3s ease;\n    }\n    .memory-card{\n      border-left:4px solid var(--accent);\n      padding-left:1rem;\n      margin-bottom:1rem;\n    }\n    .tag-badge{\n      background:var(--accent);\n      color:#fff;\n      margin-right:.25rem;\n      cursor:pointer;\n    }\n    .btn-accent{\n      background:var(--accent);\n      border-color:var(--accent);\n      color:#fff;\n    }\n  &lt;\/style>\n&lt;\/head>\n&lt;body>\n  &lt;div class=\"memory-app\">\n    &lt;!-- Header -->\n    &lt;div class=\"d-flex justify-content-between align-items-center mb-4\">\n      &lt;h1 class=\"m-0\">\ud83d\udcdd Memory Recorder &lt;small class=\"h6 fw-light\">Pro&lt;\/small>&lt;\/h1>\n      &lt;div class=\"d-flex align-items-center gap-3\">\n        &lt;button id=\"statsBtn\" class=\"btn btn-outline-secondary btn-sm\">&lt;i class=\"bi bi-bar-chart\">&lt;\/i>&lt;\/button>\n        &lt;div class=\"form-check form-switch m-0\">\n          &lt;input class=\"form-check-input\" type=\"checkbox\" id=\"darkModeSwitch\">\n        &lt;\/div>\n      &lt;\/div>\n    &lt;\/div>\n\n    &lt;!-- Search &amp; Stats row -->\n    &lt;div class=\"row g-3 align-items-end mb-4\">\n      &lt;div class=\"col-md-8\">\n        &lt;label for=\"searchInput\" class=\"form-label\">\u691c\u7d22\uff08\u30c6\u30ad\u30b9\u30c8 \/ \u30bf\u30b0\uff09&lt;\/label>\n        &lt;input type=\"text\" id=\"searchInput\" class=\"form-control\" placeholder=\"\u30ad\u30fc\u30ef\u30fc\u30c9\u3067\u691c\u7d22...\">\n      &lt;\/div>\n      &lt;div class=\"col-md-4 text-md-end\">\n        &lt;p id=\"stats\" class=\"mb-0 small text-muted\">&lt;\/p>\n      &lt;\/div>\n    &lt;\/div>\n\n    &lt;!-- Input Area -->\n    &lt;div class=\"mb-3\">\n      &lt;label for=\"memoryText\" class=\"form-label\">\u65b0\u3057\u3044\u8a18\u61b6&lt;\/label>\n      &lt;textarea class=\"form-control\" id=\"memoryText\" rows=\"3\" placeholder=\"\u51fa\u6765\u4e8b\u30fb\u601d\u3044\u51fa\u306a\u3069...\">&lt;\/textarea>\n    &lt;\/div>\n    &lt;div class=\"mb-4 row g-2 align-items-end\">\n      &lt;div class=\"col-md-8\">\n        &lt;label for=\"memoryTags\" class=\"form-label\">\u30bf\u30b0\uff08\u30ab\u30f3\u30de\u533a\u5207\u308a\uff09&lt;\/label>\n        &lt;input type=\"text\" id=\"memoryTags\" class=\"form-control\" placeholder=\"\u4f8b: \u4ed5\u4e8b, \u5bb6\u65cf, \u8da3\u5473\">\n      &lt;\/div>\n      &lt;div class=\"col-md-4 d-flex gap-2 mt-md-4\">\n        &lt;button id=\"saveBtn\" class=\"btn btn-primary flex-grow-1\">&lt;i class=\"bi bi-save\">&lt;\/i> \u4fdd\u5b58&lt;\/button>\n        &lt;button id=\"voiceBtn\" class=\"btn btn-outline-secondary\" title=\"\u97f3\u58f0\u5165\u529b\">&lt;i class=\"bi bi-mic\">&lt;\/i>&lt;\/button>\n      &lt;\/div>\n    &lt;\/div>\n\n    &lt;!-- File \/ Clear row -->\n    &lt;div class=\"d-flex flex-wrap gap-2 mb-4\">\n      &lt;button id=\"exportBtn\" class=\"btn btn-outline-secondary\">&lt;i class=\"bi bi-download\">&lt;\/i> \u30a8\u30af\u30b9\u30dd\u30fc\u30c8&lt;\/button>\n      &lt;button id=\"importBtn\" class=\"btn btn-outline-secondary\">&lt;i class=\"bi bi-upload\">&lt;\/i> \u30a4\u30f3\u30dd\u30fc\u30c8&lt;\/button>\n      &lt;button id=\"clearAllBtn\" class=\"btn btn-outline-danger ms-auto\">&lt;i class=\"bi bi-trash\">&lt;\/i> \u5168\u524a\u9664&lt;\/button>\n      &lt;input type=\"file\" id=\"importFile\" accept=\"application\/json\" class=\"d-none\">\n    &lt;\/div>\n\n    &lt;hr>\n    &lt;h2 class=\"h5 mb-3\">\ud83d\udcda \u4fdd\u5b58\u3055\u308c\u305f\u8a18\u61b6&lt;\/h2>\n    &lt;div id=\"memoryList\">&lt;\/div>\n  &lt;\/div>\n\n  &lt;!-- Statistics Modal -->\n  &lt;div class=\"modal fade\" id=\"statsModal\" tabindex=\"-1\" aria-hidden=\"true\">\n    &lt;div class=\"modal-dialog modal-lg modal-dialog-centered\">\n      &lt;div class=\"modal-content\">\n        &lt;div class=\"modal-header\">\n          &lt;h5 class=\"modal-title\">&lt;i class=\"bi bi-graph-up\">&lt;\/i> \u8a18\u61b6\u7d71\u8a08&lt;\/h5>\n          &lt;button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\">&lt;\/button>\n        &lt;\/div>\n        &lt;div class=\"modal-body\">\n          &lt;canvas id=\"statsChart\" height=\"120\">&lt;\/canvas>\n        &lt;\/div>\n      &lt;\/div>\n    &lt;\/div>\n  &lt;\/div>\n\n  &lt;!-- Edit Modal -->\n  &lt;div class=\"modal fade\" id=\"editModal\" tabindex=\"-1\" aria-hidden=\"true\">\n    &lt;div class=\"modal-dialog\">\n      &lt;div class=\"modal-content\">\n        &lt;div class=\"modal-header\">\n          &lt;h5 class=\"modal-title\">\u8a18\u61b6\u3092\u7de8\u96c6&lt;\/h5>\n          &lt;button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\">&lt;\/button>\n        &lt;\/div>\n        &lt;div class=\"modal-body\">\n          &lt;div class=\"mb-3\">\n            &lt;label for=\"editText\" class=\"form-label\">\u5185\u5bb9&lt;\/label>\n            &lt;textarea id=\"editText\" class=\"form-control\" rows=\"4\">&lt;\/textarea>\n          &lt;\/div>\n          &lt;div class=\"mb-3\">\n            &lt;label for=\"editTags\" class=\"form-label\">\u30bf\u30b0\uff08\u30ab\u30f3\u30de\u533a\u5207\u308a\uff09&lt;\/label>\n            &lt;input id=\"editTags\" class=\"form-control\">\n          &lt;\/div>\n        &lt;\/div>\n        &lt;div class=\"modal-footer\">\n          &lt;button class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">\u30ad\u30e3\u30f3\u30bb\u30eb&lt;\/button>\n          &lt;button id=\"editSaveBtn\" class=\"btn btn-primary\">\u4fdd\u5b58&lt;\/button>\n        &lt;\/div>\n      &lt;\/div>\n    &lt;\/div>\n  &lt;\/div>\n\n  &lt;!-- Bootstrap JS -->\n  &lt;script src=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.3\/dist\/js\/bootstrap.bundle.min.js\" crossorigin=\"anonymous\">&lt;\/script>\n  &lt;script>\n    \/\/ --------- Constants ---------\n    const STORAGE_KEY = \"memories\";\n    const THEME_KEY   = \"prefers-dark\";\n\n    \/\/ --------- Helpers ---------\n    const $ = sel => document.querySelector(sel);\n    const modal = id => new bootstrap.Modal($(id));\n\n    const memories = {\n      list() { return JSON.parse(localStorage.getItem(STORAGE_KEY) || \"&#91;]\"); },\n      save(arr) { localStorage.setItem(STORAGE_KEY, JSON.stringify(arr)); },\n      add(obj){ const arr = this.list(); arr.push(obj); this.save(arr);} ,\n      remove(id){ this.save(this.list().filter(m=>m.id!==id));},\n      update(id,data){ const arr=this.list().map(m=>m.id===id?{...m,...data}:m); this.save(arr);} ,\n    };\n\n    const fmtDate = d => new Intl.DateTimeFormat(\"ja-JP\",{dateStyle:\"medium\",timeStyle:\"short\"}).format(d);\n\n    \/\/ --------- Rendering ---------\n    function renderMemories(filter=\"\"){\n      const listEl = $(\"#memoryList\");\n      listEl.innerHTML=\"\";\n      const all = memories.list();\n      const lower = filter.toLowerCase();\n      const visible = all.filter(m=>{\n        const tagMatch = m.tags.some(t=>t.toLowerCase().includes(lower));\n        const textMatch= m.text.toLowerCase().includes(lower);\n        return !lower || tagMatch || textMatch;\n      }).reverse();\n\n      \/\/ stats\n      $(\"#stats\").textContent=`\u5408\u8a08: ${all.length} \u4ef6`;\n\n      if(!visible.length){listEl.innerHTML='&lt;p class=\"text-muted\">\u8a72\u5f53\u3059\u308b\u8a18\u61b6\u304c\u3042\u308a\u307e\u305b\u3093\u3002&lt;\/p>';return;}\n\n      visible.forEach(m=>{\n        const card=document.createElement(\"div\");\n        card.className=\"memory-card card\";\n        card.innerHTML=`&lt;div class=\"card-body p-3\">\n          &lt;div class=\"d-flex justify-content-between align-items-start flex-wrap\">\n            &lt;h5 class=\"card-title mb-1\">${fmtDate(new Date(m.date))}&lt;\/h5>\n            &lt;div class=\"btn-group btn-group-sm\">\n              &lt;button class=\"btn btn-link text-primary\" title=\"\u7de8\u96c6\" onclick=\"openEditor('${m.id}')\">&lt;i class=\"bi bi-pencil\">&lt;\/i>&lt;\/button>\n              &lt;button class=\"btn btn-link text-danger\" title=\"\u524a\u9664\" onclick=\"deleteMemory('${m.id}')\">&lt;i class=\"bi bi-trash\">&lt;\/i>&lt;\/button>\n            &lt;\/div>\n          &lt;\/div>\n          &lt;p class=\"card-text\" style=\"white-space:pre-wrap;\">${m.text}&lt;\/p>\n          &lt;div class=\"mt-2\">${m.tags.map(t=>`&lt;span class=\\\"badge tag-badge\\\" onclick=\\\"filterTag('${t}')\\\">${t}&lt;\/span>`).join(\" \")}&lt;\/div>\n        &lt;\/div>`;\n        listEl.appendChild(card);\n      });\n    }\n\n    \/\/ --------- CRUD ---------\n    function saveMemory(){\n      const text=$(\"#memoryText\").value.trim();\n      const tagRaw=$(\"#memoryTags\").value.trim();\n      if(!text)return;\n      const tags=tagRaw?tagRaw.split(\/\\s*,\\s*\/).filter(Boolean):&#91;];\n      memories.add({id:crypto.randomUUID(),text,tags,date:new Date().toISOString()});\n      $(\"#memoryText\").value=\"\";\n      $(\"#memoryTags\").value=\"\";\n      renderMemories($(\"#searchInput\").value);\n    }\n    function deleteMemory(id){\n      if(!confirm(\"\u524a\u9664\u3057\u307e\u3059\u304b\uff1f\"))return;\n      memories.remove(id);\n      renderMemories($(\"#searchInput\").value);\n    }\n\n    \/\/ --------- Edit ---------\n    let editingId=null;\n    function openEditor(id){\n      editingId=id;\n      const m=memories.list().find(x=>x.id===id);\n      $(\"#editText\").value=m.text;\n      $(\"#editTags\").value=m.tags.join(\", \");\n      modal('#editModal').show();\n    }\n    $(\"#editSaveBtn\").addEventListener(\"click\",()=>{\n      const text=$(\"#editText\").value.trim();\n      const tags=$(\"#editTags\").value.trim().split(\/\\s*,\\s*\/).filter(Boolean);\n      memories.update(editingId,{text,tags});\n      modal('#editModal').hide();\n      renderMemories($(\"#searchInput\").value);\n    });\n\n    \/\/ --------- Filter helper ---------\n    function filterTag(tag){\n      $(\"#searchInput\").value=tag;\n      renderMemories(tag);\n    }\n\n    \/\/ --------- Export \/ Import ---------\n    function exportMemories(){\n      const blob=new Blob(&#91;JSON.stringify(memories.list(),null,2)],{type:\"application\/json\"});\n      const url=URL.createObjectURL(blob);\n      const a=document.createElement(\"a\");\n      a.href=url;a.download=\"memories.json\";a.click();URL.revokeObjectURL(url);\n    }\n    function importMemories(file){\n      const reader=new FileReader();\n      reader.onload=e=>{try{const arr=JSON.parse(e.target.result);if(Array.isArray(arr)){memories.save(&#91;...memories.list(),...arr]);renderMemories($(\"#searchInput\").value);}else throw 0;}catch{alert(\"\u7121\u52b9\u306a\u30d5\u30a1\u30a4\u30eb\u3067\u3059\");}};\n      reader.readAsText(file);\n    }\n\n    \/\/ --------- Statistics ---------\n    let chartInstance=null;\n    function openStats(){\n      const data=memories.list();\n      const counts={};\n      data.forEach(m=>{\n        const key=m.date.slice(0,7); \/\/ YYYY-MM\n        counts&#91;key]=(counts&#91;key]||0)+1;\n      });\n      const labels=Object.keys(counts).sort();\n      const values=labels.map(l=>counts&#91;l]);\n      const ctx=$(\"#statsChart\");\n      if(chartInstance)chartInstance.destroy();\n      chartInstance=new Chart(ctx,{type:'bar',data:{labels,datasets:&#91;{label:'\u8a18\u61b6\u6570',data:values}]},options:{plugins:{legend:{display:false}}}});\n      modal('#statsModal').show();\n    }\n\n    \/\/ --------- Dark Mode ---------\n    function applyTheme(dark){\n      document.documentElement.classList.toggle('dark',dark);\n      localStorage.setItem(THEME_KEY,dark?'1':'0');\n      $(\"#darkModeSwitch\").checked=dark;\n    }\n\n    \/\/ --------- Voice Input (Experimental) ---------\n    let rec=null;\n    async function startVoice(){\n      if(!('webkitSpeechRecognition'in window||'SpeechRecognition'in window)){alert('\u97f3\u58f0\u8a8d\u8b58\u975e\u5bfe\u5fdc');return;}\n      const Speech=window.SpeechRecognition||window.webkitSpeechRecognition;\n      rec=new Speech();\n      rec.lang='ja-JP';\n      rec.continuous=false;\n      rec.interimResults=false;\n      rec.onresult=e=>{$('#memoryText').value=e.results&#91;0]&#91;0].transcript;};\n      rec.start();\n    }\n\n    \/\/ --------- Init ---------\n    document.addEventListener('DOMContentLoaded',()=>{\n      \/\/ Theme\n      applyTheme(localStorage.getItem(THEME_KEY)==='1');\n\n      \/\/ Render memories\n      renderMemories();\n\n      \/\/ Listeners\n      $('#saveBtn').addEventListener('click',saveMemory);\n      $('#clearAllBtn').addEventListener('click',()=>{if(confirm('\u3059\u3079\u3066\u524a\u9664\u3057\u307e\u3059\u304b\uff1f')){localStorage.removeItem(STORAGE_KEY);renderMemories();}});\n      $('#exportBtn').addEventListener('click',exportMemories);\n      $('#importBtn').addEventListener('click',()=>$('#importFile').click());\n      $('#importFile').addEventListener('change',e=>{if(e.target.files&#91;0])importMemories(e.target.files&#91;0]);e.target.value='';});\n      $('#darkModeSwitch').addEventListener('change',e=>applyTheme(e.target.checked));\n      $('#searchInput').addEventListener('input',e=>renderMemories(e.target.value));\n      $('#memoryText').addEventListener('keydown',e=>{if(e.key==='Enter'&amp;&amp;(e.ctrlKey||e.metaKey))saveMemory();});\n      $('#statsBtn').addEventListener('click',openStats);\n      $('#voiceBtn').addEventListener('click',startVoice);\n    });\n  &lt;\/script>\n&lt;\/body>\n&lt;\/html>\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[80],"tags":[],"class_list":["post-26057","post","type-post","status-publish","format-standard","hentry","category-html"],"aioseo_notices":[],"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26057","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=26057"}],"version-history":[{"count":1,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26057\/revisions"}],"predecessor-version":[{"id":26058,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26057\/revisions\/26058"}],"wp:attachment":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=26057"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=26057"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=26057"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}