One reading item, two dated sessions and a reread that stays visible

HTML for this example

Select the code and use your copy shortcut. Changes in this preview are temporary.

<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Reading log: record the session</title><style>
*{box-sizing:border-box}body{margin:0;background:#f3f0e8;color:#30392f;font:17px/1.5 system-ui,sans-serif}main{max-width:1080px;margin:auto;padding:24px 22px 40px}h1{font-size:clamp(29px,5vw,42px);line-height:1.12;margin:10px 0 15px;max-width:22ch}h2{font-size:21px;margin:0 0 12px}h3{font-size:18px;margin:0 0 10px}p{margin:9px 0 15px}.eyebrow{font-size:14px;letter-spacing:.07em;font-weight:750;color:#6b6949}.intro{max-width:68ch}.toolbar{display:flex;flex-wrap:wrap;gap:9px;margin:16px 0}button{font:inherit;background:#fff;color:#36513a;border:1px solid #6d896b;border-radius:6px;padding:10px 13px;cursor:pointer}button[aria-pressed=true],.primary{background:#3d6144;color:#fff}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid #a45c00;outline-offset:3px}.summary{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0}.summary span{background:#e3eadb;border:1px solid #bacbb0;border-radius:6px;padding:9px 12px;font-size:15px;font-weight:700}.ledger{background:#fff;border:1px solid #c9ccb8;border-radius:9px;padding:18px;overflow-wrap:anywhere}.ledger-title{display:flex;gap:14px;justify-content:space-between;align-items:baseline;flex-wrap:wrap}.hint{font-size:15px;color:#60684f}.record{display:grid;grid-template-columns:minmax(105px,.7fr) minmax(160px,1.3fr) minmax(85px,.6fr) minmax(180px,1.5fr) minmax(95px,.6fr);gap:12px;border-top:1px solid #d9decd;padding:17px 0}.record:last-child{padding-bottom:0}.cell{min-width:0}.cell-label{display:block;font-size:13px;font-weight:750;letter-spacing:.04em;color:#687052;text-transform:uppercase;margin-bottom:7px}.actions{grid-column:1/-1;display:flex;gap:8px}.actions button{font-size:15px;padding:7px 12px}.empty{padding:18px;background:#f7f7ef;border:1px dashed #b9bda5;border-radius:6px}.source{margin:22px 0;background:#fffdf7;padding:18px 20px;border-left:4px solid #a59b6b}.source p:last-child{margin-bottom:0}.editor{margin-top:24px;background:white;border:1px solid #b8c4ac;border-radius:9px;padding:20px}.fields{display:grid;grid-template-columns:1fr 1fr;gap:14px}.field{min-width:0}.wide{grid-column:1/-1}label{display:block;font-size:15px;font-weight:750;margin-bottom:7px}input,textarea,select{display:block;width:100%;min-width:0;font:inherit;color:inherit;padding:10px;border:1px solid #8d9f81;border-radius:5px;background:#fff}textarea{min-height:90px;resize:vertical}.status{background:#edf1e7;padding:12px 14px;border-radius:6px;min-height:3em}.confirm{padding:16px;background:#fff0d3;border:1px solid #bb8d41;border-radius:7px;margin:15px 0}.export{margin-top:20px}.export textarea{font-size:15px;min-height:150px}footer{font-size:14px;color:#60684f;margin-top:22px}[hidden]{display:none!important}@media(max-width:680px){main{padding:20px 14px 30px}.record{grid-template-columns:1fr 1fr;gap:14px}.cell.item,.cell.note{grid-column:1/-1}.actions{flex-wrap:wrap}.fields{grid-template-columns:1fr}.wide{grid-column:auto}.toolbar button{flex-grow:1}.ledger,.editor{padding:16px}}
@media(min-width:681px) and (max-width:900px){.record{grid-template-columns:1fr 1fr}.cell.item,.cell.note{grid-column:1/-1}}</style></head><body><main><div class="eyebrow">READING RECORD · CURRENT-PAGE PRACTICE</div><h1>Two sessions can belong to the same reading.</h1><p class="intro">Keep the title, date and part read together. The worked records are fictional. Add or edit a session here to see the record change; nothing is submitted or saved after you leave.</p><div class="toolbar" aria-label="Choose log"><button type="button" id="worked" aria-pressed="true">Worked log</button><button type="button" id="blank" aria-pressed="false">Blank log</button></div><div class="summary" aria-label="Current log counts"><span id="session-count">2 sessions</span><span id="reread-count">1 includes rereading</span><span id="missing-count">1 duration not recorded</span></div><section class="ledger" aria-label="Reading log output"><div class="ledger-title"><h2 id="log-title">Worked reading log</h2><span class="hint">Session entries, not books completed</span></div><p class="hint" id="log-context">Both original rows refer to T1 · The Lost Label.</p><div id="records"></div><p id="empty" class="empty" hidden>No sessions yet. Add a record using the form below.</p></section><section class="source"><h2>T1 · The Lost Label</h2><p class="hint">Original practice text for this guide. No author byline, edition or page numbers are supplied.</p><p><strong>P1.</strong> Noor found an unmarked folder on the shared desk. She put it beside the other folders without giving it a name.</p><p><strong>P2.</strong> She asked Pat whose folder it was. Pat did not know, so Noor left a note: “Owner unconfirmed.”</p></section><section class="editor"><h2 id="editor-title">Add a session to the worked log</h2><p class="hint">Use the book's actual title and author for your own record. A blank duration means Not recorded; zero remains an explicitly recorded zero.</p><div class="fields"><div class="field"><label for="date">Date · YYYY-MM-DD</label><input id="date" placeholder="2026-10-21" inputmode="numeric" maxlength="10" autocomplete="off"></div><div class="field"><label for="item">Title and author, or reading label</label><input id="item" maxlength="180" placeholder="T1 · The Lost Label"></div><div class="field"><label for="location">Pages, chapter or other location</label><input id="location" maxlength="100" placeholder="P1–P2"></div><div class="field"><label for="kind">Reading session</label><select id="kind"><option value="">Choose a session type</option><option value="first">First reading</option><option value="continuation">Continuation</option><option value="reread">Reread</option><option value="mixed">Continuation with reread</option></select></div><div class="field"><label for="minutes">Minutes · optional</label><input id="minutes" inputmode="decimal" maxlength="12" placeholder="Leave blank if not recorded"></div><div class="field wide"><label for="note">Session note · optional</label><textarea id="note" maxlength="500" placeholder="Reread P1, then read P2 for the first time."></textarea></div></div><div class="toolbar"><button type="button" id="apply" class="primary">Add session</button><button type="button" id="cancel-edit" hidden>Cancel edit</button><button type="button" id="reset">Reset this log</button></div><p id="status" class="status" role="status">Worked log ready: two sessions refer to one reading item. Missing time is not zero.</p><div id="confirm" class="confirm" hidden><p id="confirm-message"></p><div class="toolbar"><button type="button" id="confirm-action">Confirm</button><button type="button" id="keep">Keep current records</button></div></div><div class="export"><h3>Your current log as text</h3><p class="hint">This text updates with the records above. Select and copy it manually into your required file or classroom system.</p><label for="plain">Reading log text</label><textarea id="plain" readonly></textarea></div></section><footer>No timer, reading-speed score, completion grade, saved account record or sharing service. Work stays in this open page only.</footer></main><script>
'use strict';
const $=id=>document.getElementById(id);
const kinds={first:'First reading',continuation:'Continuation',reread:'Reread',mixed:'Continuation with reread'};
const fieldIds=['date','item','location','kind','minutes','note'];
const original=[{uid:'t1a',date:'2026-10-19',item:'T1 · The Lost Label',location:'P1',kind:'first',minutes:'',note:'First reading of P1.'},{uid:'t1b',date:'2026-10-20',item:'T1 · The Lost Label',location:'P1–P2',kind:'mixed',minutes:'8',note:'Reread P1, then read P2 for the first time.'}];
const cloneRows=()=>original.map(row=>({...row}));const emptyDraft=()=>Object.fromEntries(fieldIds.map(id=>[id,'']));
const logs={worked:{rows:cloneRows(),draft:emptyDraft(),editId:null},blank:{rows:[],draft:emptyDraft(),editId:null}};
let current='worked',sequence=0,pending=null;
const active=()=>logs[current];
function readDraft(){return Object.fromEntries(fieldIds.map(id=>[id,$(id).value]));}
function storeDraft(){active().draft=readDraft();}
function showDraft(){for(const id of fieldIds)$(id).value=active().draft[id];$('apply').textContent=active().editId?'Update session':'Add session';$('cancel-edit').hidden=!active().editId;$('editor-title').textContent=(active().editId?'Edit a session in the ':'Add a session to the ')+(current==='worked'?'worked log':'blank log');}
function closeConfirm(){pending=null;$('confirm').hidden=true;}
function feedback(message){$('status').textContent=message;}
function makeCell(label,value,className=''){const cell=document.createElement('div');cell.className='cell '+className;const title=document.createElement('span');title.className='cell-label';title.textContent=label;const text=document.createElement('span');text.textContent=value;cell.append(title,text);return cell;}
function render(){const rows=active().rows;$('records').replaceChildren();for(const row of rows){const entry=document.createElement('article');entry.className='record';entry.dataset.row=row.uid;entry.append(makeCell('Date',row.date),makeCell('Reading item',row.item,'item'),makeCell('Part read',row.location),makeCell('Session note',row.note||kinds[row.kind],'note'),makeCell('Minutes',row.minutes===''?'Not recorded':row.minutes));const actions=document.createElement('div');actions.className='actions';for(const [mode,label] of [['edit','Edit session'],['remove','Remove session']]){const b=document.createElement('button');b.type='button';b.textContent=label;b.id=mode+'-'+row.uid;b.setAttribute('aria-label',label+' for '+row.date+' '+row.item);b.addEventListener('click',()=>mode==='edit'?edit(row.uid):askRemove(row.uid));actions.append(b);}entry.append(actions);$('records').append(entry);}
 $('empty').hidden=rows.length>0;$('session-count').textContent=rows.length+(rows.length===1?' session':' sessions');$('reread-count').textContent=rows.filter(r=>r.kind==='reread'||r.kind==='mixed').length+' includes rereading';$('missing-count').textContent=rows.filter(r=>r.minutes==='').length+' duration not recorded';$('log-title').textContent=current==='worked'?'Worked reading log':'Your blank log';$('log-context').textContent=current==='worked'?'The original worked example has two sessions of T1. Edits below change only this practice copy.':'Use your actual book details and the fields your teacher requires. These are reading sessions; no book-completion status is recorded.';$('worked').setAttribute('aria-pressed',String(current==='worked'));$('blank').setAttribute('aria-pressed',String(current==='blank'));
 const lines=['Reading log · '+(current==='worked'?'fictional worked practice':'current-page draft'),'Date | Reading item | Part read | Session type | Session note | Minutes',...rows.map(r=>[r.date,r.item,r.location,kinds[r.kind],r.note||'—',r.minutes===''?'Not recorded':r.minutes].join(' | '))];$('plain').value=lines.join('\n');showDraft();}
function changeView(view){storeDraft();current=view;closeConfirm();render();feedback((view==='worked'?'Worked log':'Blank log')+' selected. Records and unfinished form text in both views are preserved.');}
function validDate(value){if(!/^\d{4}-\d{2}-\d{2}$/.test(value))return false;const [y,m,d]=value.split('-').map(Number);if(y<1000||y>9999)return false;const date=new Date(Date.UTC(y,m-1,d));return date.getUTCFullYear()===y&&date.getUTCMonth()===m-1&&date.getUTCDate()===d;}
function apply(){storeDraft();const draft=active().draft;const fields=Object.fromEntries(fieldIds.map(id=>[id,draft[id].trim()]));const missing=[];if(!fields.date)missing.push('Date');if(!fields.item)missing.push('Title or reading label');if(!fields.location)missing.push('Reading location');if(!Object.hasOwn(kinds,fields.kind))missing.push('Session type');if(missing.length){feedback('Complete: '+missing.join(', ')+'. No record changed.');return;}if(!validDate(fields.date)){feedback('Use a real calendar date in YYYY-MM-DD format. No record changed.');return;}if(fields.minutes!==''&&(!/^\d+(?:\.\d+)?$/.test(fields.minutes)||!Number.isFinite(Number(fields.minutes)))){feedback('Minutes must be blank or a finite nonnegative number. No record changed.');return;}
 const editing=active().editId;const entry={...fields,uid:editing||'entry'+(++sequence)};if(editing){const index=active().rows.findIndex(r=>r.uid===editing);if(index<0){feedback('The selected session is no longer present. Choose a current record.');return;}active().rows[index]=entry;}else active().rows.push(entry);active().editId=null;active().draft=emptyDraft();closeConfirm();render();feedback((editing?'Session updated.':'Session added.')+' The visible log and text below now match. This is current-page work only.');}
function edit(uid){storeDraft();const row=active().rows.find(r=>r.uid===uid);if(!row)return;active().editId=uid;active().draft=Object.fromEntries(fieldIds.map(id=>[id,row[id]]));closeConfirm();showDraft();feedback('Editing '+row.date+'. Change the fields, then choose Update session. The record is unchanged until then.');$('date').focus();}
function askRemove(uid){storeDraft();const row=active().rows.find(r=>r.uid===uid);if(!row)return;pending={kind:'remove',uid,view:current};$('confirm-message').textContent='Remove the session dated '+row.date+' from this current-page log?';$('confirm-action').textContent='Remove this session';$('confirm').hidden=false;$('keep').focus();}
function askReset(){storeDraft();pending={kind:'reset',view:current};$('confirm-message').textContent=current==='worked'?'Restore the original two worked sessions and clear this view\'s unfinished form? The blank log stays unchanged.':'Clear this blank log and its unfinished form? The worked log stays unchanged.';$('confirm-action').textContent=current==='worked'?'Restore worked log':'Clear blank log';$('confirm').hidden=false;$('keep').focus();}
function confirm(){if(!pending||pending.view!==current)return;const action=pending;if(action.kind==='remove'){active().rows=active().rows.filter(r=>r.uid!==action.uid);if(active().editId===action.uid){active().editId=null;active().draft=emptyDraft();}}else{active().rows=current==='worked'?cloneRows():[];active().draft=emptyDraft();active().editId=null;}closeConfirm();render();feedback(action.kind==='remove'?'Session removed from this page. Other records are unchanged.':current==='worked'?'Original worked log restored. This view\'s form is empty; the blank log is unchanged.':'Blank log cleared. Its form is empty; the worked log is unchanged.');$('apply').focus();}
$('worked').addEventListener('click',()=>changeView('worked'));$('blank').addEventListener('click',()=>changeView('blank'));$('apply').addEventListener('click',apply);$('cancel-edit').addEventListener('click',()=>{active().editId=null;active().draft=emptyDraft();closeConfirm();showDraft();feedback('Edit canceled. The existing session is unchanged.');$('date').focus();});$('reset').addEventListener('click',askReset);$('confirm-action').addEventListener('click',confirm);$('keep').addEventListener('click',()=>{closeConfirm();feedback('Canceled. All records and unfinished form text are unchanged.');$('reset').focus();});for(const id of fieldIds)$(id).addEventListener('input',()=>{storeDraft();closeConfirm();feedback('Form changed. Choose Add session or Update session to change the visible log.');});$('kind').addEventListener('change',()=>{storeDraft();closeConfirm();feedback('Session type changed. The visible log is unchanged until you apply it.');});render();
</script></body></html>