Home » Interactive Sleep Diary
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Responsive Sleep Diary</title>
<style>
#sleep-diary table {
width: 100%;
border-collapse: collapse;
font-family: sans-serif;
margin-bottom: 40px;
}
#sleep-diary th, #sleep-diary td {
padding: 8px;
text-align: center;
vertical-align: top;
}
.timeline {
display: flex;
flex-wrap: wrap;
gap: 2px;
justify-content: center;
}
.hour-cell {
width: 36px;
height: 36px;
border: 1px solid #ccc;
font-size: 10px;
text-align: left;
line-height: 1;
position: relative;
padding: 2px;
cursor: pointer;
overflow: hidden;
}
.time-label {
font-size: 8px;
position: absolute;
top: 2px;
left: 2px;
opacity: 0.6;
}
.hour-cell:focus {
outline: 2px solid #0077cc;
}
textarea, input {
width: 100%;
box-sizing: border-box;
}
.legend-box {
background: #f9f9f9;
border: 1px solid #ddd;
padding: 10px;
margin-top: -30px;
margin-bottom: 40px;
font-size: 14px;
text-align: left;
}
.legend-box ul {
list-style: none;
padding: 0;
margin: 0;
columns: 2;
}
.legend-box li {
margin-bottom: 4px;
}
@media screen and (max-width: 768px) {
#sleep-diary table, #sleep-diary thead, #sleep-diary tbody, #sleep-diary th, #sleep-diary td, #sleep-diary tr {
display: block;
width: 100%;
}
#sleep-diary thead tr {
display: none;
}
#sleep-diary td {
margin-bottom: 12px;
text-align: left;
position: relative;
padding-left: 50%;
}
#sleep-diary td::before {
position: absolute;
top: 8px;
left: 8px;
width: 45%;
padding-right: 10px;
white-space: nowrap;
font-weight: bold;
content: attr(data-label);
}
.timeline {
justify-content: flex-start;
overflow-x: auto;
white-space: nowrap;
}
.hour-cell {
flex: 0 0 auto;
}
}
</style>
</head>
<body>
<div id="sleep-diary">
<h2>Multi-Week Sleep Diary</h2>
<p>Please complete each row daily. Use the legend to mark activities in the timeline.</p>
<label for="weeks">Number of weeks:</label>
<input type="number" id="weeks" value="2" min="1" max="12" onchange="renderDiary()" />
<div id="diary-tables"></div>
<button onclick="downloadDiary()">Download Diary (CSV)</button>
</div>
<script>
const hours = ['6pm','7pm','8pm','9pm','10pm','11pm','12am','1am','2am','3am','4am','5am','6am','7am','8am','9am','10am','11am','12pm','1pm','2pm','3pm','4pm','5pm','6pm'];
const days = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'];
function renderDiary() {
const weekCount = parseInt(document.getElementById("weeks").value);
const container = document.getElementById("diary-tables");
container.innerHTML = '';
for (let w = 1; w <= weekCount; w++) {
const table = document.createElement("table");
table.innerHTML = `
<thead>
<tr><th colspan="6">Week ${w}</th></tr>
<tr>
<th>Day<br><small>Date below</small></th>
<th>Lights Out Time</th>
<th>Total Sleep (hrs)</th>
<th>Mood</th>
<th>Notes</th>
<th>Sleep & Activity Timeline<br><small>(6pm to 6pm)</small></th>
</tr>
</thead>
<tbody>
${days.map(day => `
<tr>
<td data-label="Day">${day}<br><input type="date"></td>
<td data-label="Lights Out Time"><input type="time"></td>
<td data-label="Total Sleep (hrs)"><input type="number" step="0.1" min="0" max="24"></td>
<td data-label="Mood"><input type="text" placeholder="Mood"></td>
<td data-label="Notes"><textarea rows="2" cols="20" placeholder="Notes..."></textarea></td>
<td data-label="Timeline"><div class="timeline">${hours.map(h => `<div class='hour-cell' title='${h}' contenteditable='true'><span class='time-label'>${h}</span></div>`).join('')}</div></td>
</tr>
`).join('')}
</tbody>
`;
container.appendChild(table);
const legend = document.createElement("p");
legend.innerHTML = `
<div class="legend-box">
<strong>Activity Legend:</strong>
<ul>
<li><strong>A</strong> = Alcohol</li>
<li><strong>C</strong> = Caffeine</li>
<li><strong>P</strong> = Pills (sleep aids)</li>
<li><strong>M</strong> = Meal</li>
<li><strong>S</strong> = Snack</li>
<li><strong>X</strong> = Exercise</li>
<li><strong>T</strong> = Toilet</li>
<li><strong>N</strong> = Noise disturbance</li>
<li><strong>W</strong> = Wake alarm</li>
</ul>
</div>`;
container.appendChild(legend);
}
}
function downloadDiary() {
const tables = document.querySelectorAll("#diary-tables table");
const rows = [];
tables.forEach((table, weekIndex) => {
const weekRows = Array.from(table.querySelectorAll("tbody tr")).map(tr => {
const inputs = Array.from(tr.querySelectorAll("input, textarea"));
const timeline = Array.from(tr.querySelectorAll(".hour-cell")).map(cell => cell.innerText.replace(/\s*(6pm|7pm|8pm|9pm|10pm|11pm|12am|1am|2am|3am|4am|5am|6am|7am|8am|9am|10am|11am|12pm|1pm|2pm|3pm|4pm|5pm|6pm)/g, '').trim()).join(' | ');
return [`Week ${weekIndex + 1}`, days[weekRows.length % 7], ...inputs.map(el => el.value || ''), timeline];
});
rows.push(...weekRows);
});
const csv = ['Week,Day,Date,Lights Out,Total Sleep,Mood,Notes,Timeline']
.concat(rows.map(r => r.join(",")))
.join("\n");
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'sleep-diary.csv';
a.click();
URL.revokeObjectURL(url);
}
renderDiary();
</script>
</body>
</html>
<!-- wp:code -->
<pre class="wp-block-code"><code>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Responsive Sleep Diary</title>
<style>
#sleep-diary table {
width: 100%;
border-collapse: collapse;
font-family: sans-serif;
margin-bottom: 40px;
}
#sleep-diary th, #sleep-diary td {
padding: 8px;
text-align: center;
vertical-align: top;
}
.timeline {
display: flex;
flex-wrap: wrap;
gap: 2px;
justify-content: center;
}
.hour-cell {
width: 36px;
height: 36px;
border: 1px solid #ccc;
font-size: 10px;
text-align: left;
line-height: 1;
position: relative;
padding: 2px;
cursor: pointer;
overflow: hidden;
}
.time-label {
font-size: 8px;
position: absolute;
top: 2px;
left: 2px;
opacity: 0.6;
}
.hour-cell:focus {
outline: 2px solid #0077cc;
}
textarea, input {
width: 100%;
box-sizing: border-box;
}
.legend-box {
background: #f9f9f9;
border: 1px solid #ddd;
padding: 10px;
margin-top: -30px;
margin-bottom: 40px;
font-size: 14px;
text-align: left;
}
.legend-box ul {
list-style: none;
padding: 0;
margin: 0;
columns: 2;
}
.legend-box li {
margin-bottom: 4px;
}
@media screen and (max-width: 768px) {
#sleep-diary table, #sleep-diary thead, #sleep-diary tbody, #sleep-diary th, #sleep-diary td, #sleep-diary tr {
display: block;
width: 100%;
}
#sleep-diary thead tr {
display: none;
}
#sleep-diary td {
margin-bottom: 12px;
text-align: left;
position: relative;
padding-left: 50%;
}
#sleep-diary td::before {
position: absolute;
top: 8px;
left: 8px;
width: 45%;
padding-right: 10px;
white-space: nowrap;
font-weight: bold;
content: attr(data-label);
}
.timeline {
justify-content: flex-start;
overflow-x: auto;
white-space: nowrap;
}
.hour-cell {
flex: 0 0 auto;
}
}
</style>
</head>
<body>
<div id="sleep-diary">
<h2>Multi-Week Sleep Diary</h2>
<p>Please complete each row daily. Use the legend to mark activities in the timeline.</p>
<label for="weeks">Number of weeks:</label>
<input type="number" id="weeks" value="2" min="1" max="12" onchange="renderDiary()" />
<div id="diary-tables"></div>
<button onclick="downloadDiary()">Download Diary (CSV)</button>
</div>
<script>
const hours = ['6pm','7pm','8pm','9pm','10pm','11pm','12am','1am','2am','3am','4am','5am','6am','7am','8am','9am','10am','11am','12pm','1pm','2pm','3pm','4pm','5pm','6pm'];
const days = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'];
function renderDiary() {
const weekCount = parseInt(document.getElementById("weeks").value);
const container = document.getElementById("diary-tables");
container.innerHTML = '';
for (let w = 1; w <= weekCount; w++) {
const table = document.createElement("table");
table.innerHTML = `
<thead>
<tr><th colspan="6">Week ${w}</th></tr>
<tr>
<th>Day<br><small>Date below</small></th>
<th>Lights Out Time</th>
<th>Total Sleep (hrs)</th>
<th>Mood</th>
<th>Notes</th>
<th>Sleep & Activity Timeline<br><small>(6pm to 6pm)</small></th>
</tr>
</thead>
<tbody>
${days.map(day => `
<tr>
<td data-label="Day">${day}<br><input type="date"></td>
<td data-label="Lights Out Time"><input type="time"></td>
<td data-label="Total Sleep (hrs)"><input type="number" step="0.1" min="0" max="24"></td>
<td data-label="Mood"><input type="text" placeholder="Mood"></td>
<td data-label="Notes"><textarea rows="2" cols="20" placeholder="Notes..."></textarea></td>
<td data-label="Timeline"><div class="timeline">${hours.map(h => `<div class='hour-cell' title='${h}' contenteditable='true'><span class='time-label'>${h}</span></div>`).join('')}</div></td>
</tr>
`).join('')}
</tbody>
`;
container.appendChild(table);
const legend = document.createElement("p");
legend.innerHTML = `
<div class="legend-box">
<strong>Activity Legend:</strong>
<ul>
<li><strong>A</strong> = Alcohol</li>
<li><strong>C</strong> = Caffeine</li>
<li><strong>P</strong> = Pills (sleep aids)</li>
<li><strong>M</strong> = Meal</li>
<li><strong>S</strong> = Snack</li>
<li><strong>X</strong> = Exercise</li>
<li><strong>T</strong> = Toilet</li>
<li><strong>N</strong> = Noise disturbance</li>
<li><strong>W</strong> = Wake alarm</li>
</ul>
</div>`;
container.appendChild(legend);
}
}
function downloadDiary() {
const tables = document.querySelectorAll("#diary-tables table");
const rows = [];
tables.forEach((table, weekIndex) => {
const weekRows = Array.from(table.querySelectorAll("tbody tr")).map(tr => {
const inputs = Array.from(tr.querySelectorAll("input, textarea"));
const timeline = Array.from(tr.querySelectorAll(".hour-cell")).map(cell => cell.innerText.replace(/\s*(6pm|7pm|8pm|9pm|10pm|11pm|12am|1am|2am|3am|4am|5am|6am|7am|8am|9am|10am|11am|12pm|1pm|2pm|3pm|4pm|5pm|6pm)/g, '').trim()).join(' | ');
return [`Week ${weekIndex + 1}`, days[weekRows.length % 7], ...inputs.map(el => el.value || ''), timeline];
});
rows.push(...weekRows);
});
const csv = ['Week,Day,Date,Lights Out,Total Sleep,Mood,Notes,Timeline']
.concat(rows.map(r => r.join(",")))
.join("\n");
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'sleep-diary.csv';
a.click();
URL.revokeObjectURL(url);
}
renderDiary();
</script>
</body>
</html>
</code></pre>
<!-- /wp:code -->