{"id":198,"date":"2025-09-09T21:11:03","date_gmt":"2025-09-09T21:11:03","guid":{"rendered":"https:\/\/millionpathapp.com\/?page_id=198"},"modified":"2025-09-09T21:11:03","modified_gmt":"2025-09-09T21:11:03","slug":"%e2%9c%85-30-day-savings-challenge-%f0%9f%92%b0-2","status":"publish","type":"page","link":"https:\/\/millionpathapp.com\/de\/%e2%9c%85-30-day-savings-challenge-%f0%9f%92%b0-2\/","title":{"rendered":"\u2705 30 Day Savings Challenge \ud83d\udcb0"},"content":{"rendered":"\n<div id=\"mp-orange-30d\" dir=\"ltr\">\n  <style>\n    \/* ===== THEME (Orange) ===== *\/\n    #mp-orange-30d{\n      --orange:#ff8a00; --orangeD:#e97700; --bg:#ffffff; --text:#0f172a; --muted:#64748b; --stroke:#e5e7eb; --ok:#16a34a; --soft:#fff7ed;\n      font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,Arial; color:var(--text);\n      background:var(--orange); padding:32px 16px;\n    }\n    .wrap{max-width:1180px;margin:0 auto}\n    .hero{background:#fff;border:1px solid rgba(0,0,0,.06);border-radius:20px;padding:22px 24px;box-shadow:0 10px 30px rgba(0,0,0,.08)}\n    .brand{display:flex;align-items:center;gap:12px}\n    .logo{width:46px;height:46px;border-radius:12px;background:var(--orange);display:grid;place-items:center;color:#fff;font-weight:900;box-shadow:0 6px 16px rgba(0,0,0,.12)}\n    .brand h1{margin:0;font-size:26px;font-weight:900}\n    .tag{color:var(--muted);margin-top:4px}\n    .controls{display:grid;grid-template-columns:1fr 1fr 1fr auto auto;gap:10px;margin-top:14px}\n    .input,.btn{border-radius:12px}\n    .input{border:1px solid var(--stroke);padding:10px 12px;font-size:15px;background:#fff}\n    .btn{border:0;padding:12px 14px;font-weight:800;cursor:pointer}\n    .btn.orange{background:var(--orange);color:#fff}.btn.orange:hover{background:var(--orangeD)}\n    .btn.ghost{background:#fff;border:1px solid var(--stroke)}\n    .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-top:14px}\n    .stat{background:#fff;border:1px solid var(--stroke);border-radius:12px;padding:10px 14px;min-height:72px}\n    .stat small{display:block;color:var(--muted);font-size:12px;margin-bottom:4px}\n    .stat b{font-size:20px}\n    .barwrap{height:12px;background:#f3f4f6;border:1px solid var(--stroke);border-radius:999px;overflow:hidden}\n    .bar{height:100%;width:0%;background:var(--ok);transition:width .25s}\n    \/* List (30 days) *\/\n    .list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:16px}\n    .day{background:#fff;border:1px solid var(--stroke);border-radius:14px;display:flex;flex-direction:column}\n    .day .top{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px;border-bottom:1px solid var(--stroke)}\n    .pill{background:var(--soft);border:1px solid #ffe3c0;color:#c2410c;padding:6px 10px;border-radius:999px;font-weight:800;min-width:72px;text-align:center}\n    .day .body{display:grid;gap:8px;padding:10px 12px}\n    .sm{font-size:12px;color:var(--muted)}\n    .amount{border:1px solid var(--stroke);border-radius:10px;padding:9px 10px;font-size:14px}\n    .note{border:1px solid var(--stroke);border-radius:10px;padding:9px 10px;font-size:13px}\n    .foot{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px;border-top:1px solid var(--stroke)}\n    .check{width:20px;height:20px}\n    .tip{color:#fff;opacity:.9;font-size:13px;margin-top:10px}\n    @media (max-width:1024px){.list{grid-template-columns:1fr 1fr}}\n    @media (max-width:680px){\n      .controls{grid-template-columns:1fr 1fr;grid-auto-rows:min-content}\n      .stats{grid-template-columns:1fr 1fr}\n      .list{grid-template-columns:1fr}\n    }\n  <\/style>\n\n  <div class=\"wrap\">\n    <!-- Header -->\n    <div class=\"hero\">\n      <div class=\"brand\">\n        <div class=\"logo\">MP<\/div>\n        <div>\n          <h1>30-Day Savings Challenge<\/h1>\n          <div class=\"tag\">Choose your total challenge &#038; daily amounts \u00b7 Orange Edition<\/div>\n        <\/div>\n      <\/div>\n\n      <!-- Controls -->\n      <div class=\"controls\">\n        <input id=\"curr\" class=\"input\" type=\"text\" value=\"\u20ac\" title=\"Currency\">\n        <input id=\"totalGoal\" class=\"input\" type=\"number\" step=\"0.01\" placeholder=\"Total challenge for 30 days (e.g., 1000)\">\n        <button id=\"equal\" class=\"btn ghost\" type=\"button\">\u2194\ufe0f Auto-fill equal<\/button>\n        <button id=\"clearAll\" class=\"btn ghost\" type=\"button\">\ud83e\uddf9 Clear amounts<\/button>\n        <button id=\"export\" class=\"btn orange\" type=\"button\">\u2b07\ufe0f Export CSV<\/button>\n      <\/div>\n\n      <!-- Summary -->\n      <div class=\"stats\">\n        <div class=\"stat\"><small>Target (30 days)<\/small><b id=\"st-target\">\u20ac0.00<\/b><\/div>\n        <div class=\"stat\"><small>Planned (sum of days)<\/small><b id=\"st-planned\">\u20ac0.00<\/b><\/div>\n        <div class=\"stat\"><small>Saved so far (checked)<\/small><b id=\"st-saved\">\u20ac0.00<\/b><\/div>\n        <div class=\"stat\">\n          <small>Progress<\/small>\n          <div class=\"barwrap\" style=\"margin-top:6px\"><div id=\"st-bar\" class=\"bar\"><\/div><\/div>\n          <div class=\"sm\" id=\"st-remaining\" style=\"margin-top:6px\">Remaining vs target: \u20ac0.00<\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n\n    <!-- 30 days -->\n    <div id=\"days\" class=\"list\"><\/div>\n\n    <div class=\"tip\">Write how much to save for each day. Tick \u2705 when you\u2019ve saved it. Everything is saved on this device.<\/div>\n  <\/div>\n\n  <script>\n    (function(){\n      const $=s=>document.querySelector(s);\n      const daysWrap=$('#days');\n      const els={\n        curr:$('#curr'), goal:$('#totalGoal'), equal:$('#equal'), clear:$('#clearAll'), ex:$('#export'),\n        tTarget:$('#st-target'), tPlanned:$('#st-planned'), tSaved:$('#st-saved'), tRemain:$('#st-remaining'), bar:$('#st-bar')\n      };\n      const KEY='mp_orange_30d_custom_v1';\n\n      \/\/ state\n      let amounts = Array.from({length:30}, ()=>0);   \/\/ planned per day\n      let notes   = {};                               \/\/ {1:'...'}\n      let checks  = {};                               \/\/ {1:true}\n      let target  = 0;                                \/\/ total challenge\n\n      \/\/ money\n      const money=(n)=> (els.curr.value||'\u20ac') + Number(n||0).toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:2});\n      const sum=a=>a.reduce((s,x)=>s+Number(x||0),0);\n\n      function save(){\n        localStorage.setItem(KEY, JSON.stringify({\n          amounts, notes, checks, target, curr:els.curr.value\n        }));\n      }\n      function load(){\n        try{\n          const raw = JSON.parse(localStorage.getItem(KEY)||'{}');\n          if(Array.isArray(raw.amounts) && raw.amounts.length===30) amounts = raw.amounts.map(Number);\n          notes  = raw.notes || {};\n          checks = raw.checks || {};\n          target = Number(raw.target||0);\n          if(raw.curr) els.curr.value = raw.curr;\n        }catch{}\n        els.goal.value = target || '';\n      }\n\n      function render(){\n        \/\/ totals\n        const planned = sum(amounts);\n        const saved = amounts.reduce((s,amt,i)=> s + (checks[i+1]?Number(amt):0), 0);\n        els.tTarget.textContent  = money(target||0);\n        els.tPlanned.textContent = money(planned);\n        els.tSaved.textContent   = money(saved);\n        const remain = (target||0) - planned;\n        els.tRemain.textContent = `Remaining vs target: ${money(remain)}`;\n        const ratio = (target>0)? Math.min(100, Math.max(0, Math.round((saved\/target)*100))) : 0;\n        els.bar.style.width = ratio + '%';\n\n        \/\/ list\n        daysWrap.innerHTML='';\n        for(let d=1; d<=30; d++){\n          const card=document.createElement('div');\n          card.className='day';\n          card.innerHTML=`\n            <div class=\"top\">\n              <span class=\"pill\">Day ${d}<\/span>\n              <label style=\"display:flex;align-items:center;gap:8px\">\n                <input class=\"check\" type=\"checkbox\" data-day=\"${d}\" ${checks[d]?'checked':''}>\n                <span class=\"sm\">${checks[d]?'Saved \u2705':'Mark'}<\/span>\n              <\/label>\n            <\/div>\n            <div class=\"body\">\n              <div>\n                <div class=\"sm\">Amount to save<\/div>\n                <input class=\"amount\" type=\"number\" step=\"0.01\" min=\"0\" value=\"${amounts[d-1]||0}\" data-day=\"${d}\">\n              <\/div>\n              <div>\n                <div class=\"sm\">Note (optional)<\/div>\n                <input class=\"note\" type=\"text\" value=\"${(notes[d]||'').replace(\/\"\/g,'&quot;')}\" data-day=\"${d}\">\n              <\/div>\n            <\/div>\n            <div class=\"foot\">\n              <span class=\"sm\">Tip: keep it realistic \ud83c\udf31<\/span>\n              <button class=\"btn ghost\" data-fill=\"${d}\" style=\"padding:8px 12px;border-radius:10px\">= Fill avg<\/button>\n            <\/div>\n          `;\n          daysWrap.appendChild(card);\n        }\n        save();\n      }\n\n      \/\/ helpers\n      function averageFill(){\n        const nonZeros = amounts.filter(n=>Number(n)>0);\n        const avg = nonZeros.length ? (sum(nonZeros)\/nonZeros.length) : (target? target\/30 : 0);\n        return Math.round(avg*100)\/100;\n      }\n\n      \/\/ events\n      els.goal.addEventListener('input', ()=>{\n        target = Number(els.goal.value||0);\n        \/\/ if equal button already pressed earlier, keep user edits; only update summary\n        render();\n      });\n      els.curr.addEventListener('input', ()=> render());\n\n      \/\/ Auto-fill equal\n      els.equal.addEventListener('click', ()=>{\n        if(!(Number(target)>0)){ alert('Enter a total challenge first.'); return; }\n        const base = Math.floor((target\/30)*100)\/100;   \/\/ to cents\n        const arr = Array(30).fill(base);\n        \/\/ adjust last day to match exactly\n        const diff = Math.round((target - sum(arr))*100)\/100;\n        arr[29] = Math.max(0, Math.round((arr[29]+diff)*100)\/100);\n        amounts = arr;\n        render();\n      });\n\n      \/\/ Clear all amounts\n      els.clear.addEventListener('click', ()=>{\n        if(!confirm('Clear all daily amounts?')) return;\n        amounts = Array(30).fill(0);\n        render();\n      });\n\n      \/\/ Per-row interactions\n      daysWrap.addEventListener('input', (e)=>{\n        const amt = e.target.closest('.amount');\n        const note= e.target.closest('.note');\n        if(amt){\n          const d = +amt.getAttribute('data-day');\n          amounts[d-1] = Number(amt.value||0);\n          render();\n        }else if(note){\n          const d = +note.getAttribute('data-day');\n          notes[d] = note.value;\n          save();\n        }\n      });\n      daysWrap.addEventListener('click', (e)=>{\n        const btn = e.target.closest('[data-fill]');\n        if(btn){\n          const d = +btn.getAttribute('data-fill');\n          const avg = averageFill();\n          amounts[d-1] = avg;\n          render();\n        }\n      });\n      daysWrap.addEventListener('change', (e)=>{\n        const cb = e.target.closest('.check');\n        if(cb){\n          const d = +cb.getAttribute('data-day');\n          checks[d] = cb.checked;\n          render();\n        }\n      });\n\n      \/\/ Export CSV\n      els.ex.addEventListener('click', ()=>{\n        const rows=[['Day','PlannedAmount','Saved(checked)','Note','Currency'],\n          ...Array.from({length:30},(_,i)=>[\n            i+1,\n            amounts[i],\n            checks[i+1]?'yes':'no',\n            (notes[i+1]||'').replace(\/\"\/g,'\"\"'),\n            els.curr.value||'\u20ac'\n          ])\n        ];\n        rows.push(['Target (30d)', target,'','','']);\n        rows.push(['Planned Sum', sum(amounts),'','','']);\n        const saved = amounts.reduce((s,a,i)=> s+(checks[i+1]?Number(a):0), 0);\n        rows.push(['Saved so far', saved,'','','']);\n        const csv = rows.map(r=> r.map(f=> `\"${String(f)}\"`).join(',')).join('\\n');\n        const blob = new Blob([csv],{type:'text\/csv;charset=utf-8;'});\n        const url = URL.createObjectURL(blob);\n        const a=document.createElement('a'); a.href=url; a.download='30_day_custom_savings.csv'; a.click(); URL.revokeObjectURL(url);\n      });\n\n      \/\/ boot\n      load(); render();\n      \/\/ if target set but user hasn\u2019t filled amounts, offer equal fill once\n      if(target>0 && amounts.every(v=>!v)){ const base=Math.floor((target\/30)*100)\/100; amounts=Array(30).fill(base); const diff=Math.round((target - sum(amounts))*100)\/100; amounts[29]=Math.max(0,Math.round((amounts[29]+diff)*100)\/100); render(); }\n    })();\n  <\/script>\n<\/div>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-198","page","type-page","status-publish","hentry"],"_hostinger_reach_plugin_has_subscription_block":false,"_hostinger_reach_plugin_is_elementor":false,"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/millionpathapp.com\/de\/wp-json\/wp\/v2\/pages\/198","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/millionpathapp.com\/de\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/millionpathapp.com\/de\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/millionpathapp.com\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/millionpathapp.com\/de\/wp-json\/wp\/v2\/comments?post=198"}],"version-history":[{"count":1,"href":"https:\/\/millionpathapp.com\/de\/wp-json\/wp\/v2\/pages\/198\/revisions"}],"predecessor-version":[{"id":199,"href":"https:\/\/millionpathapp.com\/de\/wp-json\/wp\/v2\/pages\/198\/revisions\/199"}],"wp:attachment":[{"href":"https:\/\/millionpathapp.com\/de\/wp-json\/wp\/v2\/media?parent=198"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}