diff --git a/install.sh b/install.sh
old mode 100644
new mode 100755
diff --git a/public/sw.js b/public/sw.js
index 0ffd9ab..6360f83 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -1,4 +1,4 @@
-const SHELL_CACHE='fixfin-shell-v3.19';
+const SHELL_CACHE='fixfin-shell-v3.20';
const DATA_CACHE='fixfin-data-v1';
const APP_SHELL=['/','/manifest.webmanifest','/icons/icon-192.png','/icons/icon-512.png','/icons/apple-touch-icon.png'];
diff --git a/src/main.jsx b/src/main.jsx
index f43aa78..2bdbd8b 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -21,6 +21,22 @@ function parseAmount(value) {
const number = Number(normalized);
return Number.isFinite(number) ? number : Number.NaN;
}
+let modalLockDepth = 0;
+function useModalScrollLock() {
+ useEffect(() => {
+ modalLockDepth += 1;
+ document.documentElement.classList.add('modal-open');
+ document.body.classList.add('modal-open');
+ return () => {
+ modalLockDepth = Math.max(0, modalLockDepth - 1);
+ if (modalLockDepth === 0) {
+ document.documentElement.classList.remove('modal-open');
+ document.body.classList.remove('modal-open');
+ }
+ };
+ }, []);
+}
+
function uid() {
if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();
if (globalThis.crypto?.getRandomValues) {
@@ -578,6 +594,7 @@ function ScenariosPage({data,persist}){
}
function ScenarioAdjustmentDialog({data,item,onClose,onSave}){
+ useModalScrollLock();
const [name,setName]=useState(item?.name||''); const [type,setType]=useState(item?.type||'expense'); const [amount,setAmount]=useState(item?.amount??'');
const [accountId,setAccountId]=useState(item?.accountId||data.accounts[0]?.id||''); const [fromAccountId,setFromAccountId]=useState(item?.fromAccountId||data.accounts[0]?.id||''); const [toAccountId,setToAccountId]=useState(item?.toAccountId||data.accounts[1]?.id||data.accounts[0]?.id||''); const [personId,setPersonId]=useState(item?.personId||''); const [error,setError]=useState('');
function submit(e){e.preventDefault();const numeric=parseAmount(amount);if(!name.trim())return setError('Bitte eine Bezeichnung eingeben.');if(!Number.isFinite(numeric)||numeric<0)return setError('Bitte einen gültigen Betrag eingeben.');if(!personId)return setError('Bitte eine Person auswählen.');if(type==='transfer'&&fromAccountId===toAccountId)return setError('Quell- und Zielkonto müssen verschieden sein.');const value={id:item?.id||uid(),name:name.trim(),type,amount:numeric,personId:personId||null};if(type==='transfer'){value.fromAccountId=fromAccountId;value.toAccountId=toAccountId;}else value.accountId=accountId;onSave(value);}
@@ -635,6 +652,7 @@ function DataPage({data,importFile}){
function Empty({text}){return
{text}
}
function EditorDialog({dialog,data,onClose,onSave}){
+ useModalScrollLock();
const type=dialog.type,item=dialog.item; const editing=Boolean(item?.id);
const [name,setName]=useState(item?.name||''),[amount,setAmount]=useState(item?.amount??''),[accountId,setAccountId]=useState(item?.accountId||data.accounts[0]?.id||''),[fromAccountId,setFromAccountId]=useState(item?.fromAccountId||data.accounts[0]?.id||''),[toAccountId,setToAccountId]=useState(item?.toAccountId||data.accounts[1]?.id||data.accounts[0]?.id||''),[interval,setInterval]=useState(item?.interval||'monthly'),[dueMonth,setDueMonth]=useState(item?.dueMonth?String(item.dueMonth):''),[categoryId,setCategoryId]=useState(item?.categoryId||''),[newCategoryName,setNewCategoryName]=useState(''),[personId,setPersonId]=useState(item?.personId||''),[newPersonName,setNewPersonName]=useState(''),[pushoverReminder,setPushoverReminder]=useState(item?.pushoverReminder===true),[endDate,setEndDate]=useState(item?.endDate||''),[amountChanges,setAmountChanges]=useState(Array.isArray(item?.amountChanges)?item.amountChanges:[]),[changeDate,setChangeDate]=useState(''),[changeAmount,setChangeAmount]=useState(''),[localError,setLocalError]=useState('');
const labels={accounts:'Konto',categories:'Kategorie',persons:'Person',incomes:'Eingang',expenses:'Ausgang',transfers:'Transfer'};
diff --git a/src/styles.css b/src/styles.css
index bd0001a..488a8e7 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -80,8 +80,8 @@ tbody tr:last-child td { border-bottom:0; }
.button-row { display:flex;gap:10px;flex-wrap:wrap; }
.button-link { text-decoration:none;display:inline-flex;align-items:center;cursor:pointer; }
.json-preview { margin:0;max-height:420px;overflow:auto;background:#0a0f17;border:1px solid #202a39;border-radius:12px;padding:14px;font-size:11px;color:#abb8c8; }
-.modal-backdrop { position:fixed;inset:0;background:#02050abf;display:grid;place-items:center;padding:20px;z-index:50;backdrop-filter:blur(5px); }
-.modal { width:min(470px,100%);background:#111722;border:1px solid #334158;border-radius:18px;padding:20px;box-shadow:0 30px 100px #0009; }
+.modal-backdrop { position:fixed;inset:0;background:#02050abf;display:grid;place-items:center;padding:20px;z-index:50;backdrop-filter:blur(5px);overflow:hidden;overscroll-behavior:contain; }
+.modal { width:min(470px,100%);max-height:calc(100dvh - 40px);overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;background:#111722;border:1px solid #334158;border-radius:18px;padding:20px;box-shadow:0 30px 100px #0009; }
.modal-head { display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:18px; }
.modal-head h2 { font-size:24px;margin-top:3px; }
.close { background:transparent;color:#8b99ab;font-size:24px;padding:2px 8px;border:0; }
@@ -97,6 +97,33 @@ tbody tr:last-child td { border-bottom:0; }
.loading { min-height:100vh;display:grid;place-items:center;color:#94a3b8; }
code { color:#b8c3ff; }
+html.modal-open, body.modal-open { overflow:hidden !important; overscroll-behavior:none; }
+
+@media (max-width: 760px) {
+ .modal-backdrop {
+ display:flex;
+ align-items:center;
+ justify-content:center;
+ padding:max(10px, env(safe-area-inset-top)) 10px max(10px, env(safe-area-inset-bottom));
+ }
+ .modal {
+ width:100%;
+ max-height:calc(100dvh - max(20px, env(safe-area-inset-top)) - max(20px, env(safe-area-inset-bottom)));
+ padding:18px;
+ border-radius:17px;
+ }
+ .modal-actions {
+ position:sticky;
+ bottom:-18px;
+ z-index:6;
+ margin:20px -18px -18px;
+ padding:12px 18px calc(12px + env(safe-area-inset-bottom));
+ background:linear-gradient(180deg,#111722e8 0%,#111722 28%);
+ border-top:1px solid #263246;
+ }
+ .modal-actions button { min-height:44px; }
+}
+
@media (max-width: 1050px) {
.summary-grid { grid-template-columns:1fr 1fr; }
.account-summary-grid { grid-template-columns:1fr 1fr; }
diff --git a/start-local.sh b/start-local.sh
old mode 100644
new mode 100755