Utoljára aktív 4 hours ago

jordan's Avatar jordan gist felülvizsgálása 4 hours ago. Revízióhoz ugrás

1 file changed, 193 insertions

savings.html(fájl létrehozva)

@@ -0,0 +1,193 @@
1 + <!DOCTYPE html>
2 + <html
3 + editmode="false"
4 + pageowner="false"
5 + savestatus="saved"
6 + >
7 +
8 + <head>
9 + <title>Savings Goals</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 + .goal fieldset { margin-bottom: 1rem; }
17 + .goal label { display: block; margin-bottom: 0.5rem; font-size: 0.85rem; color: #555; }
18 + .goal label input { display: block; margin-top: 0.25rem; width: 100%; max-width: 12rem; }
19 + input[type="number"] { font-size: 1rem; } /* prevent iOS auto-zoom on focus */
20 + </style>
21 + </head>
22 +
23 + <body style="">
24 +
25 + <h1>Savings Goals</h1>
26 +
27 + <p>
28 + <label>
29 + Total Savings ($):
30 + <input
31 + type="number"
32 + id="total-savings"
33 + value="0"
34 + min="0"
35 + step="0.01"
36 + oninput="this.setAttribute('value', this.value); updateSummary()"
37 + >
38 + </label>
39 + </p>
40 +
41 + <hr>
42 +
43 + <p save-ignore="">
44 + Allocated: <strong id="sum-allocated">$0.00</strong>
45 + &nbsp;|&nbsp;
46 + Remaining: <strong id="sum-remaining">$0.00</strong>
47 + </p>
48 + <p
49 + id="alloc-warning"
50 + style="color: red; display: none;"
51 + save-ignore=""
52 + >
53 + ⚠ Total allocated exceeds your savings.
54 + Over by <strong id="over-by">$0.00</strong>.
55 + </p>
56 +
57 + <hr>
58 +
59 + <h2>Goals</h2>
60 +
61 + <div
62 + id="goals-list"
63 + sortable=""
64 + >
65 + <!-- Hidden template — cloned by addGoal() -->
66 + <div
67 + class="goal"
68 + id="goal-template"
69 + style="display: none;"
70 + save-ignore=""
71 + >
72 + <fieldset>
73 + <legend>
74 + <input
75 + type="text"
76 + class="goal-name"
77 + placeholder="Goal name"
78 + value=""
79 + oninput="this.setAttribute('value', this.value)"
80 + >
81 + </legend>
82 + <p><label>
83 + Allocated from savings ($):
84 + <input
85 + type="number"
86 + class="goal-allocated"
87 + value="0"
88 + min="0"
89 + step="0.01"
90 + oninput="this.setAttribute('value', this.value); updateSummary()"
91 + >
92 + </label></p>
93 + <p><label>
94 + Goal total ($):
95 + <input
96 + type="number"
97 + class="goal-amount"
98 + value="0"
99 + min="0"
100 + step="0.01"
101 + oninput="this.setAttribute('value', this.value)"
102 + >
103 + </label></p>
104 + <p><button
105 + type="button"
106 + onclick="deleteGoal(this)"
107 + >Delete</button></p>
108 + </fieldset>
109 + </div>
110 +
111 + <button
112 + type="button"
113 + id="add-goal-btn"
114 + onclick="addGoal()"
115 + save-ignore=""
116 + >+ Add Goal</button>
117 + </div>
118 +
119 + <script>
120 + function addGoal() {
121 + const template = document.getElementById('goal-template');
122 + const clone = template.cloneNode(true);
123 +
124 + clone.id = 'goal-' + Date.now();
125 + clone.removeAttribute('save-ignore');
126 + clone.style.display = '';
127 +
128 + // Reset inputs to defaults in the clone
129 + clone.querySelector('.goal-name').value = '';
130 + clone.querySelector('.goal-name').setAttribute('value', '');
131 + clone.querySelector('.goal-amount').value = '0';
132 + clone.querySelector('.goal-amount').setAttribute('value', '0');
133 + clone.querySelector('.goal-allocated').value = '0';
134 + clone.querySelector('.goal-allocated').setAttribute('value', '0');
135 +
136 + const goalsList = document.getElementById('goals-list');
137 + const addBtn = document.getElementById('add-goal-btn');
138 + goalsList.insertBefore(clone, addBtn);
139 + updateSummary();
140 + }
141 +
142 + function deleteGoal(btn) {
143 + consent('Delete this goal?', () => {
144 + btn.closest('.goal').remove();
145 + updateSummary();
146 + });
147 + }
148 +
149 + function updateSummary() {
150 + const savings = parseFloat(document.getElementById('total-savings').value) || 0;
151 + const allocInputs = document.querySelectorAll('.goal:not(#goal-template) .goal-allocated');
152 + const allocated = Array.from(allocInputs).reduce((sum, el) => sum + (parseFloat(el.value) || 0), 0);
153 + const remaining = savings - allocated;
154 +
155 + document.getElementById('sum-allocated').textContent = '$' + allocated.toFixed(2);
156 + document.getElementById('sum-remaining').textContent = '$' + remaining.toFixed(2);
157 +
158 + const overBy = allocated - savings;
159 + const warning = document.getElementById('alloc-warning');
160 + if (overBy > 0.001) {
161 + document.getElementById('over-by').textContent = '$' + overBy.toFixed(2);
162 + warning.style.display = '';
163 + } else {
164 + warning.style.display = 'none';
165 + }
166 + }
167 +
168 + document.addEventListener('DOMContentLoaded', updateSummary);
169 +
170 + // Fast save: 500ms after last input or structural change
171 + let saveTimer;
172 + function scheduleSave() {
173 + clearTimeout(saveTimer);
174 + saveTimer = setTimeout(() => window.hyperclay.savePage(), 500);
175 + }
176 +
177 + window.addEventListener('load', () => {
178 + document.addEventListener('input', e => {
179 + if (e.target.matches('#total-savings, .goal-allocated, .goal-amount, .goal-name')) {
180 + scheduleSave();
181 + }
182 + });
183 + new MutationObserver(scheduleSave).observe(
184 + document.getElementById('goals-list'),
185 + { childList: true }
186 + );
187 + });
188 +
189 + </script>
190 +
191 + </body>
192 +
193 + </html>
Újabb Régebbi