Original fictional three-night packing practice with partial quantities and bag filters

HTML for this example

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

<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Three-night trip packing practice</title><style>*{box-sizing:border-box}body{margin:0;color:#202b39;background:#f4f1e9;font:17px/1.55 system-ui,sans-serif}main{max-width:850px;margin:auto;padding:30px 22px}h1{font:700 34px/1.15 system-ui}h2{font-size:23px}p{max-width:70ch}.note{border-left:4px solid #9b581f;padding-left:14px}.summary{font-weight:700;font-size:22px}.row{padding:20px 0;border-bottom:1px solid #b9b5ab}.row h2{margin:0 0 6px}.fields{display:flex;gap:20px;align-items:center;flex-wrap:wrap}label{display:block}input,select{font:inherit;padding:8px;border:1px solid #6d7380;border-radius:4px;max-width:100%}input{width:100px}.error{color:#992d22;font-weight:600}footer{margin-top:28px} [hidden]{display:none!important}@media(max-width:500px){main{padding:22px 18px}h1{font-size:29px}.fields{gap:12px}}</style><main><p>ORIGINAL FICTIONAL PRACTICE LIST</p><h1>Count what is inside the bag</h1><p>Three nights · hotel · train · no laundry planned. Six item types demonstrate the method; this is not a complete essentials list.</p><p class="note">Temporary browser practice only. Changes are not saved or submitted. Count packed items, not items lying beside the bag.</p><p id="progress" class="summary" aria-live="polite"></p><label for="bag">Show bag</label><select id="bag"><option>All bags</option><option>Suitcase</option><option>Day bag</option></select><p id="visible"></p><section class="row" data-bag="Suitcase" data-need="3"><h2>T-shirts</h2><p>Suitcase · Required: 3 shirts</p><div class="fields"><label for="shirts">Packed quantity <input id="shirts" type="number" min="0" max="3" step="1" value="1" aria-describedby="shirts-status"></label><span id="shirts-status" class="status" aria-live="polite"></span></div></section><section class="row" data-bag="Suitcase" data-need="3"><h2>Socks</h2><p>Suitcase · Required: 3 pairs</p><div class="fields"><label for="socks">Packed quantity <input id="socks" type="number" min="0" max="3" step="1" value="3" aria-describedby="socks-status"></label><span id="socks-status" class="status" aria-live="polite"></span></div></section><section class="row" data-bag="Suitcase" data-need="1"><h2>Sleepwear</h2><p>Suitcase · Required: 1 set</p><div class="fields"><label for="sleepwear">Packed quantity <input id="sleepwear" type="number" min="0" max="1" step="1" value="0" aria-describedby="sleepwear-status"></label><span id="sleepwear-status" class="status" aria-live="polite"></span></div></section><section class="row" data-bag="Day bag" data-need="1"><h2>Phone cable</h2><p>Day bag · Required: 1 cable</p><div class="fields"><label for="cable">Packed quantity <input id="cable" type="number" min="0" max="1" step="1" value="1" aria-describedby="cable-status"></label><span id="cable-status" class="status" aria-live="polite"></span></div></section><section class="row" data-bag="Day bag" data-need="1"><h2>Notebook</h2><p>Day bag · Required: 1 notebook</p><div class="fields"><label for="notebook">Packed quantity <input id="notebook" type="number" min="0" max="1" step="1" value="0" aria-describedby="notebook-status"></label><span id="notebook-status" class="status" aria-live="polite"></span></div></section><section class="row" data-bag="Day bag" data-need="1"><h2>Light layer</h2><p>Day bag · Required: 1 layer</p><div class="fields"><label for="layer">Packed quantity <input id="layer" type="number" min="0" max="1" step="1" value="0" aria-describedby="layer-status"></label><span id="layer-status" class="status" aria-live="polite"></span></div></section><footer><p>Complete means the entire required quantity is inside the named bag. Filtering never changes the overall summary.</p></footer></main><script>const rows=[...document.querySelectorAll(".row")],bag=document.querySelector("#bag");function update(){let complete=0,invalid=0,visible=0;for(const row of rows){const input=row.querySelector("input"),raw=input.value.trim(),value=Number(raw),need=Number(row.dataset.need),bad=raw===""||!Number.isInteger(value)||value<0||value>need;const status=row.querySelector(".status");if(bad){invalid++;status.textContent="Enter a whole number from 0 to "+need+".";status.classList.add("error");input.setAttribute("aria-invalid","true");}else{if(value===need)complete++;status.textContent=value===need?"Fully packed":(need-value)+" still to pack";status.classList.remove("error");input.removeAttribute("aria-invalid");}row.hidden=bag.value!=="All bags"&&row.dataset.bag!==bag.value;if(!row.hidden)visible++;}document.querySelector("#visible").textContent=visible+" item types shown";document.querySelector("#progress").textContent=invalid?"Check "+invalid+" count"+(invalid===1?"":"s")+" before reviewing the list.":complete+" of 6 item types fully packed";}bag.addEventListener("change",update);rows.forEach(row=>row.querySelector("input").addEventListener("input",update));update();</script></html>