<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ひとこと履歴書 Ultra</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: "Helvetica Neue", sans-serif;
background: #f0f0f0;
padding: 30px;
max-width: 900px;
margin: auto;
}
h1 {
text-align: center;
margin-bottom: 20px;
}
textarea, input[type="date"], input[type="text"] {
width: 100%;
padding: 10px;
font-size: 1rem;
margin-bottom: 10px;
}
.emotion-btn {
font-size: 20px;
padding: 6px 12px;
border: 2px solid #ccc;
background: white;
cursor: pointer;
border-radius: 6px;
}
.emotion-btn.selected {
border-color: #4caf50;
background: #e8f5e9;
}
button.add {
background: #4caf50;
color: white;
border: none;
border-radius: 6px;
padding: 10px 20px;
cursor: pointer;
font-size: 1rem;
margin-top: 10px;
}
button.export {
background: #2196f3;
margin-left: 10px;
}
.entry {
background: white;
padding: 10px;
border-left: 5px solid #ccc;
margin: 10px 0;
border-radius: 6px;
}
.section-date {
font-weight: bold;
margin-top: 30px;
}
.entry .meta {
font-size: 0.8em;
color: #666;
}
#stats {
margin: 20px 0;
background: #fff;
padding: 10px;
border-radius: 8px;
box-shadow: 0 0 3px rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<h1>ひとこと履歴書 Ultra</h1>
<textarea id="entryInput" placeholder="今日の一言を記録しよう!"></textarea>
<input type="date" id="entryDate">
<div>
<button class="emotion-btn" data-emotion="😊">😊 喜</button>
<button class="emotion-btn" data-emotion="😢">😢 哀</button>
<button class="emotion-btn" data-emotion="😠">😠 怒</button>
<button class="emotion-btn" data-emotion="😐">😐 中立</button>
</div>
<button class="add" onclick="addEntry()">記録</button>
<button class="add export" onclick="exportCSV()">CSVダウンロード</button>
<input type="text" id="searchBox" placeholder="キーワード検索(例:嬉しい、美術館)">
<div id="stats"></div>
<canvas id="emotionChart" height="200"></canvas>
<div id="entryList"></div>
<script>
const input = document.getElementById("entryInput");
const dateInput = document.getElementById("entryDate");
const searchBox = document.getElementById("searchBox");
const entryList = document.getElementById("entryList");
const stats = document.getElementById("stats");
const emotionButtons = document.querySelectorAll(".emotion-btn");
let selectedEmotion = "😊";
dateInput.valueAsDate = new Date();
emotionButtons.forEach(btn => {
btn.addEventListener("click", () => {
emotionButtons.forEach(b => b.classList.remove("selected"));
btn.classList.add("selected");
selectedEmotion = btn.dataset.emotion;
});
});
function addEntry() {
const text = input.value.trim();
const date = dateInput.value;
if (!text || !date || !selectedEmotion) return;
const entries = JSON.parse(localStorage.getItem("entries") || "[]");
entries.push({ text, date, emotion: selectedEmotion, timestamp: new Date().toISOString() });
localStorage.setItem("entries", JSON.stringify(entries));
input.value = "";
renderEntries();
}
function exportCSV() {
const entries = JSON.parse(localStorage.getItem("entries") || "[]");
let csv = "日付,感情,テキスト,記録日時\n";
entries.forEach(e => {
csv += `${e.date},${e.emotion},"${e.text.replace(/"/g, '""')}",${e.timestamp}\n`;
});
const blob = new Blob([csv], { type: "text/csv" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "hitokoto_entries.csv";
a.click();
URL.revokeObjectURL(url);
}
function groupByDate(entries) {
const grouped = {};
entries.forEach(entry => {
if (!grouped[entry.date]) grouped[entry.date] = [];
grouped[entry.date].push(entry);
});
return grouped;
}
function renderEntries() {
const entries = JSON.parse(localStorage.getItem("entries") || "[]").reverse();
const keyword = searchBox.value.trim();
const filtered = keyword
? entries.filter(e => e.text.includes(keyword))
: entries;
const grouped = groupByDate(filtered);
const emotionCounts = { "😊": 0, "😢": 0, "😠": 0, "😐": 0 };
entryList.innerHTML = "";
let totalTextLength = 0;
for (const date in grouped) {
const section = document.createElement("div");
section.innerHTML = `<div class="section-date">📅 ${date}</div>`;
grouped[date].forEach(entry => {
emotionCounts[entry.emotion]++;
totalTextLength += entry.text.length;
const div = document.createElement("div");
div.className = "entry";
div.innerHTML = `
<div>${entry.emotion} ${entry.text}</div>
<div class="meta">${new Date(entry.timestamp).toLocaleString()}</div>
`;
section.appendChild(div);
});
entryList.appendChild(section);
}
const total = filtered.length;
const avgLen = total ? Math.round(totalTextLength / total) : 0;
stats.innerHTML = `📌 総投稿数: ${total} 件|平均文字数: ${avgLen} 字`;
renderChart(emotionCounts);
}
function renderChart(counts) {
const ctx = document.getElementById("emotionChart").getContext("2d");
if (window.myChart) window.myChart.destroy();
window.myChart = new Chart(ctx, {
type: "pie",
data: {
labels: ["😊 喜", "😢 哀", "😠 怒", "😐 中立"],
datasets: [{
data: [
counts["😊"],
counts["😢"],
counts["😠"],
counts["😐"]
],
backgroundColor: ["gold", "skyblue", "tomato", "gray"]
}]
},
options: {
plugins: { legend: { position: "bottom" } }
}
});
}
searchBox.addEventListener("input", renderEntries);
renderEntries();
</script>
</body>
</html>