coffee.html
· 4.7 KiB · HTML
Bruto
<!DOCTYPE html>
<html
editmode="false"
pageowner="false"
savestatus="saved"
>
<head>
<title>Coffee Inventory</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.jsdelivr.net/npm/hyperclayjs@1.24.3/src/hyperclay.js?features=save-core,save-system,autosave,unsaved-warning,edit-mode-helpers,persist,snapshot,option-visibility,edit-mode,event-attrs,ajax-elements,sortable,movable,dom-helpers,input-helpers,onaftersave,save-freeze,dialogs,toast,the-modal,mutation,nearest,cookie,throttle,debounce,cache-bust,dom-ready,all-js,style-injection,form-data,hyper-morph,slugify,copy-to-clipboard,query-params,send-message,file-upload,live-sync,export-to-window,view-mode-excludes-edit-modules" type="module"></script>
<style data-name="option-visibility" mutations-ignore=""></style>
<style>
body { max-width: 600px; margin: 0 auto; padding: 1rem; }
.box fieldset { margin-bottom: 1rem; }
.box label { display: block; margin-bottom: 0.5rem; font-size: 0.85rem; color: #555; }
input[type="number"] { font-size: 1rem; width: 3rem; text-align: center; }
</style>
</head>
<body style="">
<h1>Coffee inventory</h1>
<p>This is a small app to keep track of how many Cometeer coffee pods I have left per box.</p>
<p save-ignore="">
Total: <strong id="total">0 pods (0 boxes)</strong>
</p>
<hr>
<div
id="boxes-list"
sortable=""
>
<!-- Hidden template — cloned by addBox() -->
<div
class="box"
id="box-template"
style="display: none;"
save-ignore=""
>
<fieldset>
<legend>
<input
type="text"
class="box-name"
placeholder="Coffee name"
value=""
oninput="this.setAttribute('value', this.value)"
>
</legend>
<p><label>
Pods remaining:
<br>
<button
type="button"
onclick="decrement(this)"
>-</button>
<input
type="number"
class="pod-count"
value="8"
min="0"
max="8"
oninput="this.setAttribute('value', this.value); updateTotal()"
>
<button
type="button"
onclick="increment(this)"
>+</button>
/ 8
</label></p>
<p><button
type="button"
onclick="deleteBox(this)"
>Delete</button></p>
</fieldset>
</div>
</div>
<button
type="button"
onclick="addBox()"
>+ Add Box</button>
<script>
function increment(btn) {
const input = btn.closest('fieldset').querySelector('.pod-count');
const n = parseInt(input.value);
if (n < 8) {
input.value = n + 1;
input.setAttribute('value', n + 1);
updateTotal();
}
}
function decrement(btn) {
const input = btn.closest('fieldset').querySelector('.pod-count');
const n = parseInt(input.value);
if (n > 0) {
input.value = n - 1;
input.setAttribute('value', n - 1);
updateTotal();
}
}
function deleteBox(btn) {
consent('Delete this box?', () => {
btn.closest('.box').remove();
updateTotal();
});
}
function addBox() {
const template = document.getElementById('box-template');
const clone = template.cloneNode(true);
clone.id = 'box-' + Date.now();
clone.removeAttribute('save-ignore');
clone.style.display = '';
clone.querySelector('.box-name').value = '';
clone.querySelector('.box-name').setAttribute('value', '');
clone.querySelector('.pod-count').value = '8';
clone.querySelector('.pod-count').setAttribute('value', '8');
document.getElementById('boxes-list').appendChild(clone);
updateTotal();
}
function updateTotal() {
const inputs = document.querySelectorAll('.box:not(#box-template) .pod-count');
const total = Array.from(inputs).reduce((sum, el) => sum + (parseInt(el.value) || 0), 0);
document.getElementById('total').textContent = total + ' pods (' + inputs.length + ' boxes)';
}
document.addEventListener('DOMContentLoaded', updateTotal);
// Fast save: 500ms after last input or structural change
let saveTimer;
function scheduleSave() {
clearTimeout(saveTimer);
saveTimer = setTimeout(() => window.hyperclay.savePage(), 500);
}
window.addEventListener('load', () => {
document.addEventListener('input', e => {
if (e.target.matches('.pod-count, .box-name')) {
scheduleSave();
}
});
new MutationObserver(scheduleSave).observe(
document.getElementById('boxes-list'),
{ childList: true }
);
});
</script>
</body>
</html>
| 1 | <!DOCTYPE html> |
| 2 | <html |
| 3 | editmode="false" |
| 4 | pageowner="false" |
| 5 | savestatus="saved" |
| 6 | > |
| 7 | |
| 8 | <head> |
| 9 | <title>Coffee Inventory</title> |
| 10 | <meta charset="utf-8"> |
| 11 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 12 | <script src="https://cdn.jsdelivr.net/npm/hyperclayjs@1.24.3/src/hyperclay.js?features=save-core,save-system,autosave,unsaved-warning,edit-mode-helpers,persist,snapshot,option-visibility,edit-mode,event-attrs,ajax-elements,sortable,movable,dom-helpers,input-helpers,onaftersave,save-freeze,dialogs,toast,the-modal,mutation,nearest,cookie,throttle,debounce,cache-bust,dom-ready,all-js,style-injection,form-data,hyper-morph,slugify,copy-to-clipboard,query-params,send-message,file-upload,live-sync,export-to-window,view-mode-excludes-edit-modules" type="module"></script> |
| 13 | <style data-name="option-visibility" mutations-ignore=""></style> |
| 14 | <style> |
| 15 | body { max-width: 600px; margin: 0 auto; padding: 1rem; } |
| 16 | .box fieldset { margin-bottom: 1rem; } |
| 17 | .box label { display: block; margin-bottom: 0.5rem; font-size: 0.85rem; color: #555; } |
| 18 | input[type="number"] { font-size: 1rem; width: 3rem; text-align: center; } |
| 19 | </style> |
| 20 | </head> |
| 21 | |
| 22 | <body style=""> |
| 23 | |
| 24 | <h1>Coffee inventory</h1> |
| 25 | <p>This is a small app to keep track of how many Cometeer coffee pods I have left per box.</p> |
| 26 | |
| 27 | <p save-ignore=""> |
| 28 | Total: <strong id="total">0 pods (0 boxes)</strong> |
| 29 | </p> |
| 30 | |
| 31 | <hr> |
| 32 | |
| 33 | <div |
| 34 | id="boxes-list" |
| 35 | sortable="" |
| 36 | > |
| 37 | |
| 38 | <!-- Hidden template — cloned by addBox() --> |
| 39 | <div |
| 40 | class="box" |
| 41 | id="box-template" |
| 42 | style="display: none;" |
| 43 | save-ignore="" |
| 44 | > |
| 45 | <fieldset> |
| 46 | <legend> |
| 47 | <input |
| 48 | type="text" |
| 49 | class="box-name" |
| 50 | placeholder="Coffee name" |
| 51 | value="" |
| 52 | oninput="this.setAttribute('value', this.value)" |
| 53 | > |
| 54 | </legend> |
| 55 | <p><label> |
| 56 | Pods remaining: |
| 57 | <br> |
| 58 | <button |
| 59 | type="button" |
| 60 | onclick="decrement(this)" |
| 61 | >-</button> |
| 62 | <input |
| 63 | type="number" |
| 64 | class="pod-count" |
| 65 | value="8" |
| 66 | min="0" |
| 67 | max="8" |
| 68 | oninput="this.setAttribute('value', this.value); updateTotal()" |
| 69 | > |
| 70 | <button |
| 71 | type="button" |
| 72 | onclick="increment(this)" |
| 73 | >+</button> |
| 74 | / 8 |
| 75 | </label></p> |
| 76 | <p><button |
| 77 | type="button" |
| 78 | onclick="deleteBox(this)" |
| 79 | >Delete</button></p> |
| 80 | </fieldset> |
| 81 | </div> |
| 82 | |
| 83 | </div> |
| 84 | |
| 85 | <button |
| 86 | type="button" |
| 87 | onclick="addBox()" |
| 88 | >+ Add Box</button> |
| 89 | |
| 90 | <script> |
| 91 | function increment(btn) { |
| 92 | const input = btn.closest('fieldset').querySelector('.pod-count'); |
| 93 | const n = parseInt(input.value); |
| 94 | if (n < 8) { |
| 95 | input.value = n + 1; |
| 96 | input.setAttribute('value', n + 1); |
| 97 | updateTotal(); |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | function decrement(btn) { |
| 102 | const input = btn.closest('fieldset').querySelector('.pod-count'); |
| 103 | const n = parseInt(input.value); |
| 104 | if (n > 0) { |
| 105 | input.value = n - 1; |
| 106 | input.setAttribute('value', n - 1); |
| 107 | updateTotal(); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | function deleteBox(btn) { |
| 112 | consent('Delete this box?', () => { |
| 113 | btn.closest('.box').remove(); |
| 114 | updateTotal(); |
| 115 | }); |
| 116 | } |
| 117 | |
| 118 | function addBox() { |
| 119 | const template = document.getElementById('box-template'); |
| 120 | const clone = template.cloneNode(true); |
| 121 | clone.id = 'box-' + Date.now(); |
| 122 | clone.removeAttribute('save-ignore'); |
| 123 | clone.style.display = ''; |
| 124 | clone.querySelector('.box-name').value = ''; |
| 125 | clone.querySelector('.box-name').setAttribute('value', ''); |
| 126 | clone.querySelector('.pod-count').value = '8'; |
| 127 | clone.querySelector('.pod-count').setAttribute('value', '8'); |
| 128 | document.getElementById('boxes-list').appendChild(clone); |
| 129 | updateTotal(); |
| 130 | } |
| 131 | |
| 132 | function updateTotal() { |
| 133 | const inputs = document.querySelectorAll('.box:not(#box-template) .pod-count'); |
| 134 | const total = Array.from(inputs).reduce((sum, el) => sum + (parseInt(el.value) || 0), 0); |
| 135 | document.getElementById('total').textContent = total + ' pods (' + inputs.length + ' boxes)'; |
| 136 | } |
| 137 | |
| 138 | document.addEventListener('DOMContentLoaded', updateTotal); |
| 139 | |
| 140 | // Fast save: 500ms after last input or structural change |
| 141 | let saveTimer; |
| 142 | function scheduleSave() { |
| 143 | clearTimeout(saveTimer); |
| 144 | saveTimer = setTimeout(() => window.hyperclay.savePage(), 500); |
| 145 | } |
| 146 | |
| 147 | window.addEventListener('load', () => { |
| 148 | document.addEventListener('input', e => { |
| 149 | if (e.target.matches('.pod-count, .box-name')) { |
| 150 | scheduleSave(); |
| 151 | } |
| 152 | }); |
| 153 | new MutationObserver(scheduleSave).observe( |
| 154 | document.getElementById('boxes-list'), |
| 155 | { childList: true } |
| 156 | ); |
| 157 | }); |
| 158 | </script> |
| 159 | |
| 160 | </body> |
| 161 | |
| 162 | </html> |