{"id":26162,"date":"2025-09-07T12:55:31","date_gmt":"2025-09-07T03:55:31","guid":{"rendered":"http:\/\/www.tyosuke20xx.com\/blog\/?p=26162"},"modified":"2025-09-07T12:55:33","modified_gmt":"2025-09-07T03:55:33","slug":"maillite-%e3%82%b7%e3%83%b3%e3%83%97%e3%83%abweb%e3%83%a1%e3%83%bc%e3%83%ab","status":"publish","type":"post","link":"http:\/\/www.tyosuke20xx.com\/blog\/?p=26162","title":{"rendered":"MailLite \u2014 \u30b7\u30f3\u30d7\u30ebWeb\u30e1\u30fc\u30eb"},"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>MailLite \u2014 \u30b7\u30f3\u30d7\u30ebWeb\u30e1\u30fc\u30eb&lt;\/title>\n&lt;style>\n  :root{\n    --bg:#f6f7fb;\n    --panel:#ffffff;\n    --text:#1f2937;\n    --muted:#6b7280;\n    --primary:#4f46e5;\n    --primary-weak:#eef2ff;\n    --border:#e5e7eb;\n    --danger:#ef4444;\n    --success:#10b981;\n    --warning:#f59e0b;\n  }\n  .dark{\n    --bg:#0b0e15;\n    --panel:#0f1623;\n    --text:#e5e7eb;\n    --muted:#9ca3af;\n    --primary:#8b5cf6;\n    --primary-weak:#221a36;\n    --border:#1f2937;\n    --danger:#f87171;\n    --success:#34d399;\n    --warning:#fbbf24;\n  }\n  *{box-sizing:border-box}\n  html,body{height:100%}\n  body{\n    margin:0;background:var(--bg);color:var(--text);\n    font:14px\/1.6 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,\"Noto Sans JP\",sans-serif;\n  }\n  .app{\n    display:grid;grid-template-rows:56px 1fr;height:100%;\n  }\n  \/* Topbar *\/\n  .topbar{\n    display:flex;align-items:center;gap:12px;\n    padding:0 16px;border-bottom:1px solid var(--border);background:var(--panel);\n    position:sticky;top:0;z-index:5;\n  }\n  .logo{\n    display:flex;align-items:center;gap:10px;font-weight:700;\n    letter-spacing:.2px;\n  }\n  .badge{font-size:10px;padding:2px 6px;border-radius:999px;background:var(--primary-weak);color:var(--primary)}\n  .search{\n    margin-left:auto;display:flex;align-items:center;gap:8px;background:var(--bg);\n    padding:6px 10px;border-radius:10px;border:1px solid var(--border);min-width:220px;max-width:460px;flex:1;\n  }\n  .search input{border:none;background:transparent;outline:none;color:var(--text);width:100%}\n  .icon{width:18px;height:18px;display:inline-block;flex:0 0 18px}\n  .btn{\n    display:inline-flex;align-items:center;gap:8px;padding:8px 12px;border-radius:10px;\n    border:1px solid var(--border);background:var(--panel);cursor:pointer;color:var(--text);\n  }\n  .btn.primary{background:var(--primary);border-color:var(--primary);color:#fff}\n  .btn.ghost{background:transparent}\n  .btn:disabled{opacity:.6;cursor:not-allowed}\n\n  \/* Layout *\/\n  .layout{\n    display:grid;grid-template-columns:260px 360px 1fr;gap:12px;padding:12px;height:calc(100vh - 56px);\n  }\n  .panel{background:var(--panel);border:1px solid var(--border);border-radius:14px;overflow:hidden;display:flex;flex-direction:column;min-height:0}\n  .sidebar{padding:12px}\n  .compose-block{padding:12px}\n  .compose-button{width:100%;justify-content:center}\n  .nav-group{margin-top:8px}\n  .nav-item{\n    display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:10px;color:var(--text);text-decoration:none;cursor:pointer;\n  }\n  .nav-item:hover{background:var(--primary-weak)}\n  .nav-item.active{background:var(--primary);color:#fff}\n  .nav-item .count{margin-left:auto;opacity:.8}\n\n  \/* List *\/\n  .list-toolbar{display:flex;align-items:center;gap:8px;padding:8px;border-bottom:1px solid var(--border)}\n  .list{overflow:auto}\n  .msg{\n    display:grid;grid-template-columns:24px 1fr auto;gap:10px;padding:12px;border-bottom:1px solid var(--border);cursor:pointer;\n  }\n  .msg:hover{background:var(--primary-weak)}\n  .msg.unread{background:linear-gradient(0deg,transparent,transparent), var(--panel);font-weight:600}\n  .msg .from{color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .msg .subject{color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .msg .snippet{color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .msg .meta{display:flex;flex-direction:column;align-items:end;gap:4px;color:var(--muted)}\n  .chip{display:inline-flex;align-items:center;gap:6px;padding:2px 8px;border-radius:999px;border:1px solid var(--border);font-size:11px}\n  .star{cursor:pointer;opacity:.7}\n  .star.active{opacity:1}\n  .avatar{\n    width:24px;height:24px;border-radius:50%;background:linear-gradient(135deg,var(--primary),#22c1c3);\n    display:grid;place-items:center;color:#fff;font-size:12px;font-weight:700;\n  }\n\n  \/* Reader *\/\n  .reader-head{\n    padding:12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-wrap:wrap;\n  }\n  .reader-title{font-size:18px;font-weight:700}\n  .reader-meta{color:var(--muted);font-size:12px}\n  .reader-actions{margin-left:auto;display:flex;gap:8px}\n  .reader-body{padding:16px;overflow:auto}\n  .empty{display:grid;place-items:center;height:100%;color:var(--muted)}\n\n  \/* Modal *\/\n  .modal{\n    position:fixed;inset:0;background:rgba(0,0,0,.4);display:none;align-items:center;justify-content:center;z-index:20;\n  }\n  .modal.open{display:flex}\n  .modal-card{\n    width:min(920px,94vw);max-height:88vh;overflow:auto;background:var(--panel);border:1px solid var(--border);\n    border-radius:16px;\n  }\n  .modal-head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border)}\n  .modal-body{padding:16px;display:grid;gap:10px}\n  .field{display:grid;gap:6px}\n  .input, textarea{\n    width:100%;padding:10px 12px;border-radius:10px;border:1px solid var(--border);\n    background:transparent;color:var(--text);outline:none;\n  }\n  textarea{min-height:220px;resize:vertical}\n  .row{display:flex;gap:10px;flex-wrap:wrap}\n  .grow{flex:1}\n\n  \/* Responsive *\/\n  @media (max-width: 1100px){\n    .layout{grid-template-columns:220px 1fr}\n    .reader{display:none}\n    .layout.show-reader .list{display:none}\n    .layout.show-reader .reader{display:flex}\n  }\n  @media (max-width: 640px){\n    .layout{grid-template-columns:1fr}\n    .sidebar{display:none}\n  }\n&lt;\/style>\n&lt;\/head>\n&lt;body>\n&lt;div class=\"app\">\n  &lt;!-- Topbar -->\n  &lt;div class=\"topbar\">\n    &lt;div class=\"logo\">\n      &lt;svg class=\"icon\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n        &lt;path d=\"M3 7.5 12 13l9-5.5v9A2.5 2.5 0 0 1 18.5 19h-13A2.5 2.5 0 0 1 3 16.5v-9Z\" stroke=\"currentColor\" stroke-width=\"1.5\"\/>\n        &lt;path d=\"m3 7.5 9-5 9 5\" stroke=\"currentColor\" stroke-width=\"1.5\"\/>\n      &lt;\/svg>\n      MailLite &lt;span class=\"badge\">beta&lt;\/span>\n    &lt;\/div>\n\n    &lt;div class=\"search\">\n      &lt;svg class=\"icon\" viewBox=\"0 0 24 24\" fill=\"none\">&lt;path d=\"m21 21-4.2-4.2\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"\/>&lt;circle cx=\"11\" cy=\"11\" r=\"7\" stroke=\"currentColor\" stroke-width=\"1.5\"\/>&lt;\/svg>\n      &lt;input id=\"search\" placeholder=\"\u30e1\u30fc\u30eb\u3092\u691c\u7d22\uff08\u5dee\u51fa\u4eba\u30fb\u4ef6\u540d\u30fb\u672c\u6587\u30fb\u30e9\u30d9\u30eb\uff09\" \/>\n      &lt;button class=\"btn ghost\" id=\"clearSearch\" title=\"\u691c\u7d22\u30af\u30ea\u30a2\">\u30af\u30ea\u30a2&lt;\/button>\n    &lt;\/div>\n\n    &lt;button class=\"btn\" id=\"toggleDark\" title=\"\u30c0\u30fc\u30af\u30e2\u30fc\u30c9\">\n      &lt;svg class=\"icon\" viewBox=\"0 0 24 24\" fill=\"none\">&lt;path d=\"M12 3a9 9 0 1 0 9 9 7 7 0 0 1-9-9Z\" stroke=\"currentColor\" stroke-width=\"1.5\"\/>&lt;\/svg>\n      \u4e3b\u9898\n    &lt;\/button>\n    &lt;button class=\"btn primary\" id=\"composeBtn\">\n      &lt;svg class=\"icon\" viewBox=\"0 0 24 24\" fill=\"none\">&lt;path d=\"M4 20h16M6 14l9.5-9.5a2.1 2.1 0 1 1 3 3L9 17l-5 1 2-4Z\" stroke=\"#fff\" stroke-width=\"1.5\" stroke-linejoin=\"round\"\/>&lt;\/svg>\n      \u65b0\u898f\u4f5c\u6210\n    &lt;\/button>\n  &lt;\/div>\n\n  &lt;!-- Main layout -->\n  &lt;div class=\"layout\" id=\"layout\">\n    &lt;!-- Sidebar -->\n    &lt;aside class=\"panel sidebar\">\n      &lt;div class=\"compose-block\">\n        &lt;button class=\"btn primary compose-button\" id=\"composeBtn2\">\n          &lt;svg class=\"icon\" viewBox=\"0 0 24 24\" fill=\"none\">&lt;path d=\"M4 20h16M6 14l9.5-9.5a2.1 2.1 0 1 1 3 3L9 17l-5 1 2-4Z\" stroke=\"#fff\" stroke-width=\"1.5\" stroke-linejoin=\"round\"\/>&lt;\/svg>\n          \u65b0\u898f\u30e1\u30fc\u30eb\n        &lt;\/button>\n      &lt;\/div>\n      &lt;nav class=\"nav-group\" id=\"folders\">&lt;\/nav>\n    &lt;\/aside>\n\n    &lt;!-- List -->\n    &lt;section class=\"panel\">\n      &lt;div class=\"list-toolbar\">\n        &lt;button class=\"btn\" id=\"markReadBtn\" title=\"\u65e2\u8aad\u306b\u3059\u308b\">\u65e2\u8aad&lt;\/button>\n        &lt;button class=\"btn\" id=\"markUnreadBtn\" title=\"\u672a\u8aad\u306b\u3059\u308b\">\u672a\u8aad&lt;\/button>\n        &lt;button class=\"btn\" id=\"archiveBtn\" title=\"\u30a2\u30fc\u30ab\u30a4\u30d6\">\u30a2\u30fc\u30ab\u30a4\u30d6&lt;\/button>\n        &lt;button class=\"btn\" id=\"deleteBtn\" title=\"\u524a\u9664\">\u524a\u9664&lt;\/button>\n        &lt;div style=\"margin-left:auto;display:flex;align-items:center;gap:6px;\">\n          &lt;span class=\"chip\">&lt;span class=\"dot\" style=\"width:8px;height:8px;border-radius:50%;background:var(--primary)\">&lt;\/span> \u30e9\u30d9\u30eb&lt;\/span>\n        &lt;\/div>\n      &lt;\/div>\n      &lt;div class=\"list\" id=\"list\">&lt;\/div>\n    &lt;\/section>\n\n    &lt;!-- Reader -->\n    &lt;section class=\"panel reader\" id=\"reader\">\n      &lt;div class=\"empty\" id=\"emptyState\">\u30e1\u30fc\u30eb\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044&lt;\/div>\n      &lt;div style=\"display:none;flex-direction:column;height:100%;\" id=\"readerWrap\">\n        &lt;div class=\"reader-head\">\n          &lt;div style=\"display:flex;align-items:center;gap:10px;min-width:0\">\n            &lt;div class=\"avatar\" id=\"readerAvatar\">Y&lt;\/div>\n            &lt;div style=\"min-width:0\">\n              &lt;div class=\"reader-title\" id=\"readerSubject\">\u4ef6\u540d&lt;\/div>\n              &lt;div class=\"reader-meta\" id=\"readerMeta\">From \u2013 To \u30fb \u65e5\u4ed8&lt;\/div>\n            &lt;\/div>\n          &lt;\/div>\n          &lt;div class=\"reader-actions\">\n            &lt;button class=\"btn\" id=\"replyBtn\" title=\"\u8fd4\u4fe1\">\u8fd4\u4fe1&lt;\/button>\n            &lt;button class=\"btn\" id=\"starBtn\" title=\"\u30b9\u30bf\u30fc\">\n              &lt;span id=\"starIcon\">\u2606&lt;\/span> \u30b9\u30bf\u30fc\n            &lt;\/button>\n            &lt;button class=\"btn\" id=\"archBtn\" title=\"\u30a2\u30fc\u30ab\u30a4\u30d6\">\u30a2\u30fc\u30ab\u30a4\u30d6&lt;\/button>\n            &lt;button class=\"btn\" id=\"trashBtn\" title=\"\u524a\u9664\" style=\"color:var(--danger)\">\u524a\u9664&lt;\/button>\n          &lt;\/div>\n        &lt;\/div>\n        &lt;div class=\"reader-body\">\n          &lt;div id=\"readerBody\">&lt;\/div>\n        &lt;\/div>\n      &lt;\/div>\n    &lt;\/section>\n  &lt;\/div>\n&lt;\/div>\n\n&lt;!-- Compose Modal -->\n&lt;div class=\"modal\" id=\"composeModal\" aria-hidden=\"true\">\n  &lt;div class=\"modal-card\">\n    &lt;div class=\"modal-head\">\n      &lt;strong>\u65b0\u898f\u30e1\u30c3\u30bb\u30fc\u30b8&lt;\/strong>\n      &lt;div class=\"row\">\n        &lt;button class=\"btn\" id=\"saveDraftBtn\">\u4e0b\u66f8\u304d\u4fdd\u5b58&lt;\/button>\n        &lt;button class=\"btn primary\" id=\"sendBtn\">\u9001\u4fe1&lt;\/button>\n        &lt;button class=\"btn\" id=\"closeModalBtn\">\u9589\u3058\u308b&lt;\/button>\n      &lt;\/div>\n    &lt;\/div>\n    &lt;div class=\"modal-body\">\n      &lt;div class=\"row\">\n        &lt;div class=\"field grow\">\n          &lt;label for=\"to\">\u5b9b\u5148\uff08\u30ab\u30f3\u30de\u533a\u5207\u308a\uff09&lt;\/label>\n          &lt;input class=\"input\" id=\"to\" placeholder=\"example@example.com, someone@domain.jp\" \/>\n        &lt;\/div>\n        &lt;div class=\"field\" style=\"min-width:160px;\">\n          &lt;label for=\"label\">\u30e9\u30d9\u30eb&lt;\/label>\n          &lt;input class=\"input\" id=\"label\" placeholder=\"work, personal \u306a\u3069\" \/>\n        &lt;\/div>\n      &lt;\/div>\n      &lt;div class=\"field\">\n        &lt;label for=\"subject\">\u4ef6\u540d&lt;\/label>\n        &lt;input class=\"input\" id=\"subject\" placeholder=\"\u4ef6\u540d\u3092\u5165\u529b\" \/>\n      &lt;\/div>\n      &lt;div class=\"field\">\n        &lt;label for=\"body\">\u672c\u6587&lt;\/label>\n        &lt;textarea id=\"body\" placeholder=\"\u672c\u6587\u3092\u5165\u529b\">&lt;\/textarea>\n      &lt;\/div>\n    &lt;\/div>\n  &lt;\/div>\n&lt;\/div>\n\n&lt;script>\n\/** ======= Simple Mail App (no backend) ======= *\/\nconst DB_KEY = \"maillite-db-v1\";\nconst QS = sel => document.querySelector(sel);\nconst QSA = sel => &#91;...document.querySelectorAll(sel)];\nconst state = {\n  currentFolder: \"inbox\",\n  query: \"\",\n  selectedIds: new Set(),\n  currentId: null,\n  db: { messages: &#91;] }\n};\nconst FOLDERS = &#91;\n  {id:\"inbox\",   name:\"\u53d7\u4fe1\u7bb1\",    icon:\"\ud83d\udce5\"},\n  {id:\"starred\", name:\"\u30b9\u30bf\u30fc\",    icon:\"\u2b50\"},\n  {id:\"sent\",    name:\"\u9001\u4fe1\u6e08\u307f\",  icon:\"\ud83d\udce4\"},\n  {id:\"drafts\",  name:\"\u4e0b\u66f8\u304d\",    icon:\"\ud83d\udcdd\"},\n  {id:\"archive\", name:\"\u30a2\u30fc\u30ab\u30a4\u30d6\",icon:\"\ud83d\uddc4\ufe0f\"},\n  {id:\"spam\",    name:\"\u8ff7\u60d1\",      icon:\"\ud83d\udeab\"},\n  {id:\"trash\",   name:\"\u30b4\u30df\u7bb1\",    icon:\"\ud83d\uddd1\ufe0f\"},\n];\n\nfunction initDB(){\n  const saved = localStorage.getItem(DB_KEY);\n  if(saved){\n    state.db = JSON.parse(saved);\n    return;\n  }\n  \/\/ Seed sample messages\n  const now = Date.now();\n  const demo = &#91;\n    mkMsg(\"suzuki@example.com\",\"\u3088\u3046\u3053\u305d MailLite \u3078\",\"MailLite \u3092\u304a\u8a66\u3057\u3044\u305f\u3060\u304d\u3042\u308a\u304c\u3068\u3046\u3054\u3056\u3044\u307e\u3059\uff01\\n\\n\u3053\u306e\u30e1\u30fc\u30eb\u306f\u30c7\u30e2\u3067\u3059\u3002\",&#91;\"welcome\"], now-3600_000),\n    mkMsg(\"shop@ec.example.com\",\"\u3010\u304a\u77e5\u3089\u305b\u3011\u30b5\u30de\u30fc\u30bb\u30fc\u30eb\u958b\u50ac\uff01\",\"\u6700\u5927 50%OFF\u3002\u4eca\u3059\u3050\u30c1\u30a7\u30c3\u30af\uff01\",&#91;\"promo\"], now-7200_000),\n    mkMsg(\"boss@company.jp\",\"\u660e\u65e5\u306e\u6253\u5408\u305b\u8b70\u984c\",\"\u30fb\u30ea\u30ea\u30fc\u30b9\u8a08\u753b\\n\u30fb\u969c\u5bb3\u5bfe\u5fdc\\n\u30fb\u30b3\u30b9\u30c8\u898b\u76f4\u3057\",&#91;\"work\"], now-86400_000, true),\n    mkMsg(\"friend@chat.jp\",\"\u9031\u672b\u306e\u4e88\u5b9a\u3069\u3046\uff1f\",\"\u6620\u753b\u304b\u30ab\u30e9\u30aa\u30b1\u884c\u304b\u306a\u3044\uff1f\",&#91;\"personal\"], now-5400_000),\n    mkMsg(\"security@service.jp\",\"\u30ed\u30b0\u30a4\u30f3\u901a\u77e5\",\"\u65b0\u3057\u3044\u7aef\u672b\u304b\u3089\u30ed\u30b0\u30a4\u30f3\u304c\u3042\u308a\u307e\u3057\u305f\u3002\",&#91;\"security\"], now-9600_000),\n  ];\n  demo&#91;2].unread = false;\n  state.db.messages = demo;\n  persist();\n}\nfunction mkMsg(from, subject, body, labels=&#91;], time=Date.now(), starred=false){\n  const id = crypto.randomUUID();\n  const initials = (from.split(\"@\")&#91;0]&#91;0]||\"U\").toUpperCase();\n  return {\n    id, box:\"inbox\", from, to:&#91;], subject, body, labels, starred, unread:true,\n    date: time, initials\n  };\n}\nfunction persist(){ localStorage.setItem(DB_KEY, JSON.stringify(state.db)); }\n\nfunction formatDate(ts){\n  const d = new Date(ts);\n  const pad = n => String(n).padStart(2,\"0\");\n  return `${d.getFullYear()}\/${pad(d.getMonth()+1)}\/${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;\n}\n\n\/\/ Render folders\nfunction renderFolders(){\n  const el = QS(\"#folders\");\n  el.innerHTML = \"\";\n  for(const f of FOLDERS){\n    const count = countFolder(f.id);\n    const a = document.createElement(\"a\");\n    a.className = \"nav-item\" + (state.currentFolder===f.id?\" active\":\"\");\n    a.dataset.id = f.id;\n    a.innerHTML = `&lt;span>${f.icon}&lt;\/span>&lt;span>${f.name}&lt;\/span>&lt;span class=\"count\">${count||\"\"}&lt;\/span>`;\n    a.addEventListener(\"click\", ()=>{\n      state.currentFolder = f.id;\n      state.selectedIds.clear();\n      state.currentId = null;\n      render();\n    });\n    el.appendChild(a);\n  }\n}\nfunction countFolder(folder){\n  return filterByFolder(state.db.messages, folder).length;\n}\nfunction filterByFolder(list, folder){\n  switch(folder){\n    case \"inbox\":   return list.filter(m=>m.box===\"inbox\");\n    case \"starred\": return list.filter(m=>m.starred &amp;&amp; m.box!==\"trash\");\n    case \"sent\":    return list.filter(m=>m.box===\"sent\");\n    case \"drafts\":  return list.filter(m=>m.box===\"drafts\");\n    case \"archive\": return list.filter(m=>m.box===\"archive\");\n    case \"spam\":    return list.filter(m=>m.box===\"spam\");\n    case \"trash\":   return list.filter(m=>m.box===\"trash\");\n    default:        return list;\n  }\n}\n\n\/\/ Search\nfunction matchesQuery(m,q){\n  if(!q) return true;\n  const s = q.toLowerCase();\n  const hay = &#91;\n    m.from, (m.to||&#91;]).join(\",\"), m.subject, m.body, (m.labels||&#91;]).join(\",\")\n  ].join(\"\\n\").toLowerCase();\n  return hay.includes(s);\n}\n\n\/\/ Render list\nfunction renderList(){\n  const wrap = QS(\"#list\");\n  const items = filterByFolder(state.db.messages, state.currentFolder)\n    .filter(m=>matchesQuery(m, state.query))\n    .sort((a,b)=>b.date-a.date);\n  wrap.innerHTML = \"\";\n  if(items.length===0){\n    wrap.innerHTML = `&lt;div class=\"empty\" style=\"height:100%;\">${state.query? \"\u691c\u7d22\u7d50\u679c\u304c\u3042\u308a\u307e\u305b\u3093\":\"\u3053\u306e\u30d5\u30a9\u30eb\u30c0\u306f\u7a7a\u3067\u3059\"}&lt;\/div>`;\n    return;\n  }\n  for(const m of items){\n    const row = document.createElement(\"div\");\n    row.className = \"msg\" + (m.unread?\" unread\":\"\");\n    row.dataset.id = m.id;\n    const starClass = m.starred? \"active\":\"\";\n    row.innerHTML = `\n      &lt;div class=\"avatar\" title=\"${m.from}\">${m.initials}&lt;\/div>\n      &lt;div style=\"min-width:0\">\n        &lt;div class=\"from\">${m.from}&lt;\/div>\n        &lt;div class=\"subject\">${m.subject}&lt;\/div>\n        &lt;div class=\"snippet\">${(m.labels?.length? m.labels.map(l=>\"#\"+l).join(\" \")+\" \u00b7 \":\"\")}${m.body.replace(\/\\n\/g,\" \").slice(0,120)}&lt;\/div>\n      &lt;\/div>\n      &lt;div class=\"meta\">\n        &lt;div>${formatDate(m.date)}&lt;\/div>\n        &lt;div class=\"star ${starClass}\" data-star-id=\"${m.id}\" title=\"\u30b9\u30bf\u30fc\">${m.starred?\"\u2605\":\"\u2606\"}&lt;\/div>\n      &lt;\/div>\n    `;\n    row.addEventListener(\"click\", (e)=>{\n      \/\/ If star clicked, don't open\n      if(e.target &amp;&amp; e.target.dataset.starId){ return; }\n      openMessage(m.id);\n    });\n    row.querySelector(\".star\").addEventListener(\"click\",(e)=>{\n      e.stopPropagation();\n      toggleStar(m.id);\n    });\n    wrap.appendChild(row);\n  }\n}\n\n\/\/ Reader\nfunction openMessage(id){\n  const m = state.db.messages.find(x=>x.id===id);\n  if(!m) return;\n  state.currentId = id;\n  m.unread = false;\n  persist();\n  QS(\"#emptyState\").style.display = \"none\";\n  QS(\"#readerWrap\").style.display = \"flex\";\n  QS(\"#readerSubject\").textContent = m.subject || \"(\u4ef6\u540d\u306a\u3057)\";\n  QS(\"#readerMeta\").textContent    = `From: ${m.from}  \/  To: ${(m.to||&#91;]).join(\", \")||\"(\u306a\u3057)\"} \u30fb ${formatDate(m.date)}`; \n  QS(\"#readerBody\").innerHTML      = safeHtml(m.body).replace(\/\\n\/g,\"&lt;br>\");\n  QS(\"#readerAvatar\").textContent  = m.initials || \"U\";\n  QS(\"#starIcon\").textContent      = m.starred ? \"\u2605\" : \"\u2606\";\n  \/\/ Mobile: show reader\n  QS(\"#layout\").classList.add(\"show-reader\");\n  render();\n}\n\nfunction safeHtml(s=\"\"){\n  return s.replace(\/&#91;&amp;&lt;>\"']\/g, ch => ({\n    \"&amp;\":\"&amp;amp;\",\"&lt;\":\"&amp;lt;\",\">\":\"&amp;gt;\",'\"':\"&amp;quot;\",\"'\":\"&amp;#39;\"\n  }&#91;ch]));\n}\n\n\/\/ Actions\nfunction getSelectedOrCurrentIds(){\n  if(state.selectedIds.size>0) return &#91;...state.selectedIds];\n  if(state.currentId) return &#91;state.currentId];\n  return &#91;];\n}\nfunction markRead(read=true){\n  for(const id of getSelectedOrCurrentIds()){\n    const m = state.db.messages.find(x=>x.id===id);\n    if(m) m.unread = !read;\n  }\n  persist(); render();\n}\nfunction moveTo(box){\n  for(const id of getSelectedOrCurrentIds()){\n    const m = state.db.messages.find(x=>x.id===id);\n    if(m) m.box = box;\n  }\n  \/\/ If current message was moved out of view, clear reader\n  if(state.currentId){\n    const cm = state.db.messages.find(x=>x.id===state.currentId);\n    const visible = filterByFolder(&#91;cm], state.currentFolder).length>0;\n    if(!visible){ state.currentId = null; QS(\"#readerWrap\").style.display=\"none\"; QS(\"#emptyState\").style.display=\"grid\"; }\n  }\n  persist(); render();\n}\nfunction toggleStar(id){\n  const m = state.db.messages.find(x=>x.id===id);\n  if(m){ m.starred = !m.starred; persist(); render(); if(state.currentId===id) QS(\"#starIcon\").textContent=m.starred?\"\u2605\":\"\u2606\"; }\n}\n\n\/\/ Compose\nfunction openCompose(prefill={}){\n  QS(\"#composeModal\").classList.add(\"open\");\n  QS(\"#to\").value      = prefill.to?.join(\", \") || \"\";\n  QS(\"#subject\").value = prefill.subject || \"\";\n  QS(\"#body\").value    = prefill.body || \"\";\n  QS(\"#label\").value   = (prefill.labels||&#91;]).join(\", \");\n}\nfunction closeCompose(){ QS(\"#composeModal\").classList.remove(\"open\"); }\n\nfunction saveDraft(){\n  const draft = collectForm();\n  draft.box = \"drafts\";\n  draft.unread = false;\n  draft.from = \"me@local\";\n  draft.id = crypto.randomUUID();\n  state.db.messages.push(draft);\n  persist(); closeCompose(); render();\n  alert(\"\u4e0b\u66f8\u304d\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f\u3002\");\n}\nfunction sendMail(){\n  const msg = collectForm();\n  if(!msg.to.length){ alert(\"\u5b9b\u5148\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\"); return; }\n  msg.box = \"sent\";\n  msg.unread = false;\n  msg.from = \"me@local\";\n  msg.id = crypto.randomUUID();\n  state.db.messages.push(msg);\n  persist(); closeCompose(); render();\n  alert(\"\u9001\u4fe1\u3057\u307e\u3057\u305f\uff08\u30c7\u30e2\u52d5\u4f5c\uff1a\u5b9f\u9001\u4fe1\u306a\u3057\uff09\");\n}\nfunction collectForm(){\n  const to = QS(\"#to\").value.split(\",\").map(s=>s.trim()).filter(Boolean);\n  const subject = QS(\"#subject\").value.trim() || \"(\u4ef6\u540d\u306a\u3057)\";\n  const body = QS(\"#body\").value;\n  const labels = QS(\"#label\").value.split(\",\").map(s=>s.trim()).filter(Boolean);\n  return { to, subject, body, labels, date: Date.now(), starred:false, unread:true, initials:\"M\" };\n}\n\n\/\/ Selection (click+Ctrl\/Shift optional simplified)\nQS(\"#list\").addEventListener(\"click\",(e)=>{\n  const row = e.target.closest(\".msg\");\n  if(!row) return;\n  if(e.ctrlKey || e.metaKey){\n    const id = row.dataset.id;\n    if(state.selectedIds.has(id)) state.selectedIds.delete(id); else state.selectedIds.add(id);\n    row.classList.toggle(\"selected\");\n  }\n});\n\n\/\/ Topbar controls\nQS(\"#composeBtn\").onclick = ()=>openCompose();\nQS(\"#composeBtn2\").onclick = ()=>openCompose();\nQS(\"#closeModalBtn\").onclick = closeCompose;\nQS(\"#saveDraftBtn\").onclick = saveDraft;\nQS(\"#sendBtn\").onclick = sendMail;\n\nQS(\"#search\").addEventListener(\"input\",(e)=>{ state.query = e.target.value.trim(); renderList(); });\nQS(\"#clearSearch\").onclick = ()=>{ QS(\"#search\").value=\"\"; state.query=\"\"; renderList(); };\n\nQS(\"#toggleDark\").onclick = ()=>{\n  document.body.classList.toggle(\"dark\");\n  localStorage.setItem(\"maillite-theme\", document.body.classList.contains(\"dark\")? \"dark\":\"light\");\n};\n\n\/\/ List toolbar actions\nQS(\"#markReadBtn\").onclick = ()=>markRead(true);\nQS(\"#markUnreadBtn\").onclick = ()=>markRead(false);\nQS(\"#archiveBtn\").onclick = ()=>moveTo(\"archive\");\nQS(\"#deleteBtn\").onclick = ()=>moveTo(\"trash\");\n\n\/\/ Reader actions\nQS(\"#replyBtn\").onclick = ()=>{\n  const m = state.db.messages.find(x=>x.id===state.currentId);\n  if(!m) return;\n  openCompose({ to:&#91;m.from], subject:\"Re: \"+m.subject, body:`\\n\\n--- ${m.from} \u3055\u3093\u306e\u30e1\u30c3\u30bb\u30fc\u30b8 ---\\n${m.body}`, labels:&#91;\"reply\"] });\n};\nQS(\"#starBtn\").onclick = ()=>{ if(state.currentId) toggleStar(state.currentId); };\nQS(\"#archBtn\").onclick = ()=>moveTo(\"archive\");\nQS(\"#trashBtn\").onclick = ()=>moveTo(\"trash\");\n\n\/\/ Clicking outside modal to close\nQS(\"#composeModal\").addEventListener(\"click\",(e)=>{ if(e.target===QS(\"#composeModal\")) closeCompose(); });\n\n\/\/ Mobile back from reader by clicking empty area (or press Escape)\ndocument.addEventListener(\"keydown\",(e)=>{\n  if(e.key===\"Escape\"){\n    if(QS(\"#composeModal\").classList.contains(\"open\")) closeCompose();\n    else QS(\"#layout\").classList.remove(\"show-reader\");\n  }\n});\n\n\/\/ Initial render\nfunction render(){\n  renderFolders();\n  renderList();\n}\n(function boot(){\n  initDB();\n  render();\n  \/\/ Theme\n  if(localStorage.getItem(\"maillite-theme\")===\"dark\"){ document.body.classList.add(\"dark\"); }\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,4,6],"tags":[3],"class_list":["post-26162","post","type-post","status-publish","format-standard","hentry","category-html","category-programming","category-web","tag-programming"],"aioseo_notices":[],"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26162","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=26162"}],"version-history":[{"count":1,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26162\/revisions"}],"predecessor-version":[{"id":26163,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/26162\/revisions\/26163"}],"wp:attachment":[{"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=26162"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=26162"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.tyosuke20xx.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=26162"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}