diff --git a/index.html b/index.html
index 86c971a..323dd13 100644
--- a/index.html
+++ b/index.html
@@ -232,6 +232,87 @@
}
.md-body a:hover { opacity: .8; }
+ /* ── Diary panel ── */
+ .diary-panel {
+ position: fixed;
+ top: 0; right: 0;
+ width: 380px;
+ height: 100dvh;
+ background: var(--surface);
+ border-left: 1px solid var(--border);
+ display: flex;
+ flex-direction: column;
+ z-index: 200;
+ transform: translateX(100%);
+ transition: transform .22s cubic-bezier(.4,0,.2,1);
+ }
+ .diary-panel.open { transform: translateX(0); }
+
+ .diary-panel-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 16px;
+ height: 48px;
+ border-bottom: 1px solid var(--border);
+ flex-shrink: 0;
+ }
+ .diary-panel-title {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 12px;
+ font-weight: 500;
+ letter-spacing: .1em;
+ color: var(--text1);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ .diary-status-dot {
+ width: 7px; height: 7px;
+ border-radius: 50%;
+ background: var(--text3);
+ transition: background .3s;
+ flex-shrink: 0;
+ }
+ .diary-status-dot.connected { background: var(--accent); }
+ .diary-status-dot.pending { background: #FCD34D; }
+
+ .diary-textarea {
+ flex: 1;
+ width: 100%;
+ resize: none;
+ border: none;
+ outline: none;
+ background: transparent;
+ color: var(--text1);
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 13px;
+ line-height: 1.75;
+ padding: 20px;
+ box-sizing: border-box;
+ }
+ .diary-textarea::placeholder { color: var(--text3); }
+
+ .diary-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 16px;
+ border-top: 1px solid var(--border);
+ flex-shrink: 0;
+ }
+ .diary-sync-status {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 10px;
+ color: var(--text3);
+ }
+ .diary-sync-status.saved { color: var(--accent); }
+ .diary-sync-status.pending { color: #FCD34D; }
+
+ @media (max-width: 640px) {
+ .diary-panel { width: 100%; }
+ }
+
/* ── Mobile ── */
@media (max-width: 640px) {
.pane-wrap { grid-template-columns: 1fr; }
@@ -291,9 +372,14 @@
-
+
+
+
+
@@ -328,6 +414,29 @@
+
+
+
@@ -415,6 +524,104 @@
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').catch(() => {});
}
+
+ // ── Diary panel ──────────────────────────────────────────
+ const DIARY_URL = 'http://localhost:2626/diary';
+ const DIARY_LS_KEY = 'posimai-log-diary';
+ const diaryPanel = document.getElementById('diaryPanel');
+ const diaryBtn = document.getElementById('diaryBtn');
+ const diaryCloseBtn= document.getElementById('diaryCloseBtn');
+ const diaryTextarea= document.getElementById('diaryTextarea');
+ const diaryDot = document.getElementById('diaryDot');
+ const diarySyncStatus = document.getElementById('diarySyncStatus');
+ const diaryCharCount = document.getElementById('diaryCharCount');
+
+ let serverOnline = false;
+ let saveTimer = null;
+
+ function setDiaryStatus(state, text) {
+ diarySyncStatus.className = 'diary-sync-status ' + state;
+ diarySyncStatus.textContent = text;
+ diaryDot.className = 'diary-status-dot ' + (serverOnline ? 'connected' : state === 'pending' ? 'pending' : '');
+ }
+
+ // サーバー接続確認 & 未同期ローカルデータのプッシュ
+ async function checkServer() {
+ try {
+ const res = await fetch(DIARY_URL, { signal: AbortSignal.timeout(800) });
+ if (!res.ok) throw new Error();
+ serverOnline = true;
+ diaryDot.className = 'diary-status-dot connected';
+ // ローカルに保留中のデータがあればサーバーに送る
+ const local = localStorage.getItem(DIARY_LS_KEY);
+ if (local !== null) {
+ const serverData = await res.json();
+ if (local !== serverData.content) {
+ await fetch(DIARY_URL, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ content: local }) });
+ }
+ }
+ } catch {
+ serverOnline = false;
+ diaryDot.className = 'diary-status-dot' + (localStorage.getItem(DIARY_LS_KEY) ? ' pending' : '');
+ }
+ }
+
+ // パネルを開くときにコンテンツをロード
+ async function openDiary() {
+ diaryPanel.classList.add('open');
+ diaryBtn.setAttribute('aria-expanded', 'true');
+ setDiaryStatus('', 'connecting...');
+ await checkServer();
+ try {
+ if (serverOnline) {
+ const res = await fetch(DIARY_URL, { signal: AbortSignal.timeout(800) });
+ const { content } = await res.json();
+ diaryTextarea.value = content;
+ localStorage.setItem(DIARY_LS_KEY, content);
+ setDiaryStatus('saved', 'synced');
+ } else {
+ diaryTextarea.value = localStorage.getItem(DIARY_LS_KEY) || '';
+ setDiaryStatus('pending', 'offline — saved locally');
+ }
+ } catch {
+ diaryTextarea.value = localStorage.getItem(DIARY_LS_KEY) || '';
+ setDiaryStatus('pending', 'offline — saved locally');
+ }
+ diaryCharCount.textContent = diaryTextarea.value.length + ' chars';
+ diaryTextarea.focus();
+ }
+
+ function closeDiary() {
+ diaryPanel.classList.remove('open');
+ diaryBtn.setAttribute('aria-expanded', 'false');
+ }
+
+ // 入力のたびに debounce 保存 (1s)
+ diaryTextarea.addEventListener('input', () => {
+ const content = diaryTextarea.value;
+ diaryCharCount.textContent = content.length + ' chars';
+ localStorage.setItem(DIARY_LS_KEY, content);
+ setDiaryStatus('pending', 'saving...');
+ clearTimeout(saveTimer);
+ saveTimer = setTimeout(async () => {
+ if (serverOnline) {
+ try {
+ await fetch(DIARY_URL, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ content }) });
+ setDiaryStatus('saved', 'saved to diary.md');
+ } catch {
+ serverOnline = false;
+ setDiaryStatus('pending', 'offline — saved locally');
+ }
+ } else {
+ setDiaryStatus('pending', 'offline — saved locally');
+ }
+ }, 1000);
+ });
+
+ diaryBtn.addEventListener('click', openDiary);
+ diaryCloseBtn.addEventListener('click', closeDiary);
+ // overlay クリックでも閉じる
+ document.getElementById('overlay').addEventListener('click', closeDiary);
})();