<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>いきいきMAP</title> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css"/> <style> /* Base Styles & Layout */ html, body { height: 100%; margin: 0; padding: 0; font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; /* Modern font */ color: #333; background-color: #f4f7f6; /* Light background for the whole page */ } #container { display: flex; height: 100vh; width: 100vw; overflow: hidden; /* Prevent overall scrollbars */ } /* Sidebar Styles */ #sidebar { width: 320px; /* Fixed width for better control */ flex-shrink: 0; /* Prevent shrinking below fixed width */ background-color: #ffffff; padding: 25px; /* More generous padding */ overflow-y: auto; border-right: 1px solid #e0e0e0; /* Softer border */ box-shadow: 3px 0 10px rgba(0, 0, 0, 0.08); /* More subtle shadow */ z-index: 1000; transition: width 0.3s ease-in-out; /* Smooth resizing */ display: flex; flex-direction: column; } #sidebar h2 { margin-top: 0; margin-bottom: 25px; color: #2e7d32; /* Deeper green */ font-size: 1.8em; /* Larger, more prominent heading */ border-bottom: 2px solid #a5d6a7; /* Underline for emphasis */ padding-bottom: 10px; text-align: center; } #comments-list { flex-grow: 1; /* Allow list to take available space */ margin-top: 15px; padding-right: 5px; /* Space for scrollbar */ } /* Map Styles */ #map { flex-grow: 1; /* Map takes remaining space */ height: 100vh; z-index: 0; background-color: #e0e0e0; /* Default map background */ } /* History Entry Styles */ .history-entry { background-color: #f8fcf9; /* Very light green background */ border: 1px solid #e8f5e9; /* Light green border */ border-radius: 8px; /* Rounded corners */ padding: 15px; margin-bottom: 15px; /* Spacing between entries */ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); /* Soft shadow */ transition: all 0.2s ease-in-out; position: relative; /* For button positioning */ } .history-entry:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); /* Enhanced shadow on hover */ transform: translateY(-2px); /* Slight lift on hover */ } .history-entry p { margin: 5px 0; font-size: 0.95em; } .history-entry p strong { color: #388e3c; /* Darker green for labels */ } /* Editable Comment Text */ .comment-text[contenteditable="true"] { border: 1px solid #c8e6c9; /* Light green border */ border-radius: 5px; padding: 8px; /* More padding */ background-color: #ffffff; /* White background for editable area */ display: block; /* Take full width */ width: calc(100% - 16px); /* Adjust for padding */ box-sizing: border-box; font-size: 0.9em; line-height: 1.5; min-height: 60px; /* Minimum height for comments */ overflow: auto; /* Enable scroll if content overflows */ word-wrap: break-word; /* Ensure long words break */ } .comment-text[contenteditable="true"]:focus { outline: 2px solid #4CAF50; /* Brighter outline on focus */ border-color: #4CAF50; } /* Delete Button in History Entry */ .history-entry button { background-color: #f44336; /* Red for delete */ color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; font-size: 0.85em; position: absolute; /* Position at top right */ top: 10px; right: 10px; transition: background-color 0.2s ease; opacity: 0; /* Hidden by default */ visibility: hidden; } .history-entry:hover button { opacity: 1; /* Visible on hover */ visibility: visible; } .history-entry button:hover { background-color: #d32f2f; /* Darker red on hover */ } /* Leaflet Popup Overrides */ .leaflet-popup-content-wrapper { background: #ffffff; border-radius: 12px; /* Smoother rounded corners */ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15); /* More pronounced shadow */ padding: 0; /* Remove default padding */ } .leaflet-popup-content { margin: 15px 20px 15px 20px; /* Adjust padding inside content */ width: 320px !important; /* Slightly adjusted for better fit */ max-width: none !important; font-size: 0.95rem; color: #444; } .leaflet-popup-content p { margin-top: 0; margin-bottom: 10px; } .leaflet-popup-content p:last-child { margin-bottom: 0; } .leaflet-popup-content textarea, .comment-input { /* Assuming comment-input is also used elsewhere for consistency */ width: calc(100% - 12px); /* Adjust for padding within text area */ height: 90px; /* Slightly less height */ font-size: 0.9rem; padding: 6px; box-sizing: border-box; border-radius: 6px; /* Slightly more rounded */ border: 1px solid #bdbdbd; /* Softer border */ resize: vertical; transition: border-color 0.2s ease; } .leaflet-popup-content textarea:focus, .comment-input:focus { border-color: #4CAF50; /* Green highlight on focus */ outline: none; } .leaflet-popup-content button { margin-top: 15px; /* More space above button */ width: 100%; padding: 12px; /* Larger click area */ font-size: 1.05rem; /* Slightly larger text */ background-color: #4CAF50; /* Vibrant green */ color: white; border: none; cursor: pointer; border-radius: 6px; /* Slightly more rounded */ transition: background-color 0.2s ease, transform 0.1s ease; } .leaflet-popup-content button:hover { background-color: #45a049; /* Darker green on hover */ transform: translateY(-1px); /* Slight lift effect */ } .leaflet-popup-tip { background: #ffffff; /* Match popup background */ box-shadow: 0 3px 14px rgba(0,0,0,0.15); /* Match popup shadow */ } </style> </head> <body> <div id="container"> <div id="sidebar"> <h2>いきいきMAP 活動履歴</h2> <div id="comments-list"></div> </div> <div id="map"></div> </div> <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script> <script> // 地図の初期化 const map = L.map('map').setView([38.9122, 139.8360], 13); // 国土地理院の地図タイルレイヤーを追加 L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', { attribution: '<a href="https://maps.gsi.go.jp/development/ichiran.html">国土地理院</a>', maxZoom: 18, minZoom: 5 }).addTo(map); window.addEventListener('load', () => { loadSavedComments(); setTimeout(() => { map.invalidateSize(); // マップのリサイズを強制 }, 100); // 遅延で確実に実行 }); // ページ読み込み時に保存されたコメントを表示 window.addEventListener('load', loadSavedComments); // マーカーを管理する配列 let markers = []; // 地図をクリックしてマーカーと情報ウィンドウを追加 map.on('click', (e) => { const { lat, lng } = e.latlng; addMarker(lat, lng); }); // マーカーとコメント入力ウィンドウを追加 function addMarker(lat, lng) { const marker = L.marker([lat, lng]).addTo(map); const popupContent = ` <div class="info-window"> <p>活動日記を入力してください:</p> <textarea id="comment" rows="3" style="width:100%;"></textarea> <button onclick="saveComment('${lat}', '${lng}')">保存</button> </div> `; marker.bindPopup(popupContent).openPopup(); } // コメントを保存し、右側の履歴に追加 function saveComment(lat, lng) { const comment = document.getElementById("comment").value; const timestamp = new Date().toLocaleString(); if (!comment.trim()) { alert("コメントを入力してください。"); return; } const entry = { lat, lng, comment, timestamp }; addCommentToHistory(entry); // 履歴に表示 saveToLocalStorage(entry); // ローカルストレージに保存 const marker = L.marker([lat, lng]).addTo(map); // マーカーを追加 markers.push(marker); // マーカーを配列に保存 marker.bindPopup(` <div class="info-window"> <p><strong>日時:</strong> ${timestamp}</p> <p><strong>コメント:</strong> ${comment}</p> </div> `); } // ローカルストレージにコメントを保存 function saveToLocalStorage(entry) { let comments = JSON.parse(localStorage.getItem('comments')) || []; comments.push(entry); localStorage.setItem('comments', JSON.stringify(comments)); } // 保存されたコメントをロードして表示 function loadSavedComments() { const comments = JSON.parse(localStorage.getItem('comments')) || []; comments.forEach((entry, index) => { addCommentToHistory(entry, index); const marker = L.marker([entry.lat, entry.lng]).addTo(map); markers[index] = marker; // マーカーを管理 marker.bindPopup(` <div class="info-window"> <p><strong>日時:</strong> ${entry.timestamp}</p> <p><strong>コメント:</strong> ${entry.comment}</p> </div> `); }); } // 履歴にコメントを追加(削除・編集ボタン付き) function addCommentToHistory({ lat, lng, comment, timestamp }, index) { const commentEntry = ` <div class="history-entry" data-index="${index}"> <p><strong>日時:</strong> ${timestamp}</p> <p><strong>場所:</strong> 緯度 ${lat}, 経度 ${lng}</p> <p><strong>活動日記:</strong> <span class="comment-text" contenteditable="true" onblur="updateComment(${index}, this)"> ${comment} </span> </p> <button onclick="deleteComment(${index})">削除</button> </div> `; document.getElementById("comments-list").innerHTML += commentEntry; } // コメントを削除(対応するマーカーも削除) function deleteComment(index) { let comments = JSON.parse(localStorage.getItem('comments')) || []; // ローカルストレージのデータを削除 comments.splice(index, 1); // 指定されたコメントを削除 localStorage.setItem('comments', JSON.stringify(comments)); // 更新されたコメントリストを保存 // 対応するマーカーを削除 if (markers[index]) { map.removeLayer(markers[index]); // 地図から削除 } // マーカー配列を更新 markers.splice(index, 1); // 配列から削除 // コメント履歴をリロード reloadComments(); } // コメントを直接編集し保存する(直接編集時に保存) function updateComment(index, element) { let comments = JSON.parse(localStorage.getItem('comments')) || []; const newComment = element.textContent.trim(); // 編集後の内容を取得(空白を除去) if (!newComment) { alert("コメントは空白にできません。元の内容に戻します。"); element.textContent = comments[index].comment; // 元のコメントに戻す return; } // コメントを更新して保存 comments[index].comment = newComment; localStorage.setItem('comments', JSON.stringify(comments)); // ローカルストレージを更新 reloadComments(); // コメント履歴をリロード } // コメント履歴をリロード function reloadComments() { document.getElementById("comments-list").innerHTML = ""; // 履歴をクリア markers.forEach(marker => map.removeLayer(marker)); // 地図上の全マーカーを削除 markers = []; // マーカー配列をリセット loadSavedComments(); // 保存されたコメントを再ロード } </script> </body> </html>