Files
accounted/prototypes/invoice-editor-redesign.prototype.html
Mattsson e11f70b347 Bug/gh issues fiz (#1103)
* refactor: optimize page loading and data fetching

* fix: resolve recurring production runtime errors

* feat: add MCP company and customer updates

* fix: handle year-end tax adjustments

* feat: harden annual report compliance

* fix: expand invoice logo and font support

* fix: sanitize API route error responses

* fix: sanitize user-facing error messages

* feat: persist onboarding and tax assessment notices

* fix: reduce cloud backup audit churn

* feat: refine invoice editor layout

* fix: show saved tax adjustments in INK2

* fix: complete annual report API mappings

* docs: record operational safeguards and decisions

* fix: harden annual report review findings

* fix: adjust column span for description based on VAT registration

* New css class name
2026-07-21 23:00:15 +02:00

282 lines
23 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="sv">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PROTOTYPE — R4 finish (F1F4)</title>
<!--
THROWAWAY PROTOTYPE. Round 5 (leaf): base = R4 (split rows, borderless,
grouped economy) with a FULL aligned header incl. Beskrivning + Belopp.
Body + header locked; only the add-row affordance and row-actions vary (2x2).
Switch: draggable picker (bottom-right) or ← / → keys.
-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Hedvig+Letters+Serif:opsz@12..24&family=Geist:wght@400;450;500;600&display=swap" rel="stylesheet">
<style>
:root{
--bg:#f4f3ef;--sheet:#ffffff;--fg:#211f1b;--muted:#7c766d;--faint:#a6a096;
--border:#e7e3db;--border-strong:#d9d4ca;--field-border:#dcd7cd;
--beige:#e9e5dc;--beige-hover:#f0ede6;--beige-soft:#f4f1ea;
--success:#5f7355;--destructive:#b0503a;--r-sm:6px;
--serif:'Hedvig Letters Serif',Georgia,serif;--sans:'Geist','Inter',system-ui,sans-serif;
--ring:0 0 0 3px rgba(120,113,108,.16);
}
*{box-sizing:border-box}html,body{margin:0;padding:0}
body{background:var(--bg);color:var(--fg);font-family:var(--sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;padding:40px 24px 150px}
.stagehead{display:flex;align-items:baseline;gap:12px;margin:0 auto 20px;max-width:1120px}
.stagehead h1{font-family:var(--serif);font-weight:400;font-size:20px;margin:0}.stagehead .sub{color:var(--muted);font-size:13px}
.stage{max-width:1120px;margin:0 auto}
.dialog{position:relative;background:var(--sheet);border:1px solid var(--border);border-radius:14px;box-shadow:0 22px 60px -28px rgba(30,27,22,.28),0 2px 8px -4px rgba(30,27,22,.12);overflow:hidden}
label{display:block;font-size:12.5px;color:var(--muted);margin-bottom:6px;font-weight:450}
.req{color:var(--destructive);margin-left:2px}
input,select,textarea{font-family:var(--sans);font-size:13.5px;color:var(--fg);width:100%;background:var(--sheet);border:1px solid var(--field-border);border-radius:var(--r-sm);height:38px;padding:0 11px;outline:none;transition:border-color .15s,box-shadow .15s}
textarea{height:auto;padding:9px 11px;resize:vertical;min-height:64px;line-height:1.55}
select{appearance:none;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%237c766d' stroke-width='2'><path d='M6 9l6 6 6-6'/></svg>");background-repeat:no-repeat;background-position:right 10px center;padding-right:30px;cursor:pointer}
input:focus,select:focus,textarea:focus{border-color:var(--border-strong);box-shadow:var(--ring)}
.num{text-align:right;font-variant-numeric:tabular-nums}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;height:38px;padding:0 15px;border-radius:var(--r-sm);border:1px solid transparent;background:transparent;font-family:var(--sans);font-size:13.5px;font-weight:500;color:var(--fg);cursor:pointer;transition:.15s;white-space:nowrap}
.btn svg{width:15px;height:15px}
.btn.primary{background:var(--fg);color:#fbfaf7;border-color:var(--fg)}.btn.primary:hover{background:#000}
.btn.secondary{background:var(--beige);color:var(--fg)}.btn.secondary:hover{background:var(--beige-hover)}
.btn.outline{border-color:var(--field-border);background:var(--sheet)}.btn.outline:hover{background:var(--beige-soft)}
.btn.ghost{color:var(--muted)}.btn.ghost:hover{background:var(--beige-soft);color:var(--fg)}
.btn.sm{height:32px;padding:0 11px;font-size:12.5px}
.iconbtn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:var(--r-sm);border:none;background:transparent;color:var(--muted);cursor:pointer;transition:.15s}
.iconbtn:hover{background:var(--beige-hover);color:var(--fg)}
.iconbtn.del:hover{color:var(--destructive)}
.tabs{display:inline-flex;background:var(--beige-soft);border:1px solid var(--border);border-radius:8px;padding:3px;gap:3px}
.tabs button{border:none;background:transparent;font-size:13px;font-weight:500;color:var(--muted);padding:6px 14px;border-radius:6px;cursor:pointer;font-family:var(--sans)}
.tabs button.on{background:var(--sheet);color:var(--fg);box-shadow:0 1px 2px rgba(30,27,22,.08)}
.seclabel{font-size:11px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--faint)}
.pill{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;color:var(--muted);background:var(--sheet);border:1px solid var(--field-border);border-radius:8px;padding:8px 12px;cursor:pointer;height:36px}
.pill:hover{background:var(--beige-soft);color:var(--fg)}.pill svg{width:13px;height:13px}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:13px}.grid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:13px}
.d3top{display:flex;align-items:center;justify-content:space-between;padding:16px 26px 6px}
.d3top h2{font-family:var(--serif);font-weight:400;font-size:21px;margin:0}
.d3band{display:flex;align-items:flex-end;gap:26px;padding:10px 26px 16px;border-bottom:1px solid var(--border);flex-wrap:wrap}
.ul-lab{font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--faint);margin-bottom:3px;display:block}
.ul-cell{display:flex;flex-direction:column;gap:3px}
.ul-cell input,.ul-cell select{border:0;border-bottom:1px solid var(--field-border);border-radius:0;background:transparent;padding-left:2px;height:34px;min-width:120px}
.ul-cell input:focus,.ul-cell select:focus{box-shadow:none;border-bottom-color:var(--fg)}
.work{padding:20px 26px 12px}.addrow{display:flex;gap:8px;margin-top:10px;flex-wrap:wrap}
.menuwrap{position:relative;display:inline-block}
.menu{position:absolute;right:0;top:36px;z-index:40;min-width:224px;background:var(--sheet);border:1px solid var(--border-strong);border-radius:10px;box-shadow:0 16px 40px -18px rgba(30,27,22,.35);padding:5px;display:none}
.menu.open{display:block}
.menu .mlabel{font-size:10.5px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--faint);padding:8px 10px 4px}
.menu button.mi{display:flex;align-items:center;gap:9px;width:100%;background:none;border:none;font-family:var(--sans);font-size:13px;color:var(--fg);text-align:left;padding:8px 10px;border-radius:6px;cursor:pointer;height:auto}
.menu button.mi:hover{background:var(--beige-soft)}.menu button.mi.danger{color:var(--destructive)}
.menu .sep{height:1px;background:var(--border);margin:5px 6px}.menu svg{width:15px;height:15px;color:var(--muted)}
.menu button.mi.danger svg{color:var(--destructive)}.pop{min-width:320px;padding:16px}
.rotstrip{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:8px;padding:9px 11px;background:var(--beige-soft);border-radius:8px}
.rottag{font-size:11px;font-weight:600;color:var(--muted);font-variant-numeric:tabular-nums}
.rotstrip select,.rotstrip input{height:32px;background:var(--sheet)}
.rotstrip .amt{margin-left:auto;font-variant-numeric:tabular-nums;color:var(--success);font-weight:500}
/* ===== R4 split rows with FULL aligned header ===== */
.srow{display:flex;align-items:flex-start;gap:14px;padding:11px 6px}
.list>.srow{border-top:1px solid var(--border)}
.srow.line:hover{background:var(--beige-soft);border-radius:8px}
.headrow{border-bottom:1px solid var(--border);padding-bottom:9px;align-items:flex-end}
.headrow.srow:hover{background:transparent}
.hlab{font-size:9.5px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--faint)}
.sgrip{width:14px;flex:none;color:transparent;cursor:grab;padding-top:8px;text-align:center}
.srow.line:hover .sgrip{color:var(--faint)}
.sdesc{flex:1;min-width:130px}
.seco{display:flex;align-items:flex-start;gap:10px}
.seco .f{display:flex;flex-direction:column;gap:3px}
.seco .f>.hlab{margin-bottom:0}
.seco input,.seco select{height:34px;font-size:12.5px}
.sbel{width:104px;flex:none;text-align:right;font-variant-numeric:tabular-nums;font-family:var(--serif);font-size:16px;padding-top:6px}
.sact{width:34px;flex:none;display:flex;justify-content:flex-end;gap:2px;padding-top:1px}
.hovacts .sact{opacity:0;transition:opacity .15s}
.hovacts .srow.line:hover .sact,.hovacts .sact:has(.menu.open),.hovacts .sact.keep{opacity:1}
.textrow input{font-style:italic;color:var(--muted)}
.ghostrow{padding:2px 6px}
.ghostadd{display:flex;align-items:center;gap:8px;width:100%;background:none;border:none;color:var(--muted);font-family:var(--sans);font-size:13px;padding:10px;cursor:pointer;border-radius:6px;text-align:left}
.ghostadd:hover{background:var(--beige-soft);color:var(--fg)}.ghostadd svg{width:15px;height:15px}
.sumbar{position:sticky;bottom:0;display:flex;align-items:center;gap:20px;padding:15px 26px;border-top:1px solid var(--border-strong);background:rgba(255,255,255,.94);backdrop-filter:blur(6px)}
.sumbar .brk{color:var(--muted);font-size:12.5px;font-variant-numeric:tabular-nums;display:flex;gap:14px}
.sumbar .pay{margin-left:auto;display:flex;align-items:baseline;gap:9px}.sumbar .pay .k{font-size:12px;color:var(--muted)}
.sumbar .pay .v{font-family:var(--serif);font-size:23px;font-variant-numeric:tabular-nums}
.sumbar .acts{display:flex;gap:9px;margin-left:8px}
.warn{display:flex;gap:10px;align-items:flex-start;background:#fbf3ec;border:1px solid #efd9c6;border-radius:8px;padding:11px 13px;font-size:13px;color:#8a5a2f;margin:14px 26px 0}
.warn svg{width:16px;height:16px;flex:none;margin-top:2px}
#picker{position:fixed;right:22px;bottom:22px;z-index:100;width:290px;background:#1c1a17;color:#f4f1ea;border-radius:14px;box-shadow:0 18px 48px -16px rgba(0,0,0,.55);font-family:var(--sans);user-select:none}
#picker .ph{display:flex;align-items:center;gap:10px;padding:12px 14px;cursor:grab}
#picker .dot{width:8px;height:8px;border-radius:50%;background:#c9b8a3}#picker .ph b{font-size:12.5px;font-weight:600}#picker .ph .grip{margin-left:auto;color:#6b6459;letter-spacing:2px}
#picker .nav{display:flex;align-items:center;gap:10px;padding:2px 12px 12px}
#picker .arrow{width:34px;height:34px;border-radius:8px;border:1px solid #3a352e;background:#26231e;color:#f4f1ea;font-size:16px;cursor:pointer}#picker .arrow:hover{background:#332f28}
#picker .cur{flex:1;text-align:center}#picker .cur .k{font-size:10.5px;color:#8a8378;letter-spacing:.08em}#picker .cur .n{font-family:var(--serif);font-size:16px}
#picker .toggles{border-top:1px solid #302c26;padding:11px 12px;display:flex;flex-direction:column;gap:9px}
#picker .tg{display:flex;align-items:center;gap:8px}#picker .tg .tl{font-size:11px;color:#8a8378;width:44px}
#picker .seg{display:flex;background:#26231e;border-radius:7px;padding:2px;gap:2px;flex:1}
#picker .seg button{flex:1;border:none;background:transparent;color:#a9a296;font-size:11.5px;padding:5px 4px;border-radius:5px;cursor:pointer;font-family:var(--sans)}
#picker .seg button.on{background:#413b32;color:#f7f3ec}
#picker .hintbar{font-size:10.5px;color:#6b6459;text-align:center;padding:0 12px 11px}
</style>
</head>
<body>
<div class="stagehead"><h1>R4 finish — rubrikrad + radåtgärder</h1><span class="sub">kropp låst · header med Beskrivning · lägg-till &amp; ⋮ varieras (2×2)</span></div>
<div class="stage"><div class="dialog" id="mount"></div></div>
<div id="picker">
<div class="ph" id="pgrip"><span class="dot"></span><b>Finish</b><span class="grip"></span></div>
<div class="nav"><button class="arrow" id="prev"></button><div class="cur"><div class="k" id="curk">F1</div><div class="n" id="curn">Spökrad + ⋮</div></div><button class="arrow" id="next"></button></div>
<div class="toggles">
<div class="tg"><span class="tl">Läge</span><div class="seg" id="segMode"><button data-m="invoice" class="on">Faktura</button><button data-m="self_billed">Självfaktura</button></div></div>
<div class="tg"><span class="tl">Moms</span><div class="seg" id="segVat"><button data-v="1" class="on">Reg.</button><button data-v="0">Ej reg.</button></div></div>
</div>
<div class="hintbar">← / → för att byta variant</div>
</div>
<script>
const CUSTOMERS=['Nordisk Design AB','Studio Ljus HB','Kaffe &amp; Co AB'];
const ARTICLES=['Egen rad','KONS-01','DES-04','LIC-12'];
const UNITS=['tim','st','dag','km','kg','mån'];
const CUR=['SEK','EUR','USD','GBP','NOK','DKK'];const VAT=['25%','12%','6%','0%'];
const ROWS=[
{desc:'Konsultarvode september',qty:'24',unit:'tim',price:'1 150,00',vat:'25%',art:'KONS-01',tot:'27 600,00',rot:false},
{desc:'Utlägg resa (SJ)',qty:'1',unit:'st',price:'480,00',vat:'25%',art:'Egen rad',tot:'480,00',rot:true},
];
let S={vi:0,mode:'invoice',vat:true};
const isSB=()=>S.mode==='self_billed';
const opts=(a,s)=>a.map((o,i)=>`<option ${((s==null&&i===0)||o===s)?'selected':''}>${o}</option>`).join('');
const sel=(a,s)=>`<select>${opts(a,s)}</select>`;
const icon={
plus:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>',
dots:'<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="12" cy="19" r="1.6"/></svg>',
trash:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14"/></svg>',
cal:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="17" rx="2"/><path d="M3 9h18M8 2v4M16 2v4"/></svg>',
bank:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 10l9-6 9 6M5 10v9M19 10v9M9 10v9M15 10v9M3 21h18"/></svg>',
tag:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3h8l10 10-8 8L3 11V3z"/><circle cx="7" cy="7" r="1.4"/></svg>',
send:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg>',
chev:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6"/></svg>',
sliders:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16"/></svg>',
warn:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l9 16H3l9-16zM12 10v4M12 17.5v.2"/></svg>',
more:'<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg>',
};
function rotStrip(){return isSB()?'':`<div class="rotstrip"><span class="rottag">ROT 30%</span>${sel(['Måleri','Snickeri','El','VVS'],'Snickeri')}<input class="num" value="8" style="max-width:70px"><span class="amt">1 440,00 kr</span></div>`}
/* row actions: 'menu' = full ⋮ ; 'inline' = hover trash + compact ⋯ (no delete) */
function rowActions(style){
const compact=`<div class="mlabel">Skattereduktion</div><button class="mi">ROT-avdrag</button><button class="mi">RUT-avdrag</button><div class="sep"></div><button class="mi">${icon.cal} Periodisering</button><button class="mi">${icon.bank} Eget försäljningskonto</button><button class="mi">${icon.tag} Dimensioner</button>`;
if(style==='inline'){
return `<button class="iconbtn del" title="Ta bort rad">${icon.trash}</button>
<div class="menuwrap"><button class="iconbtn dotsbtn" title="Mer">${icon.more}</button><div class="menu">${compact}</div></div>`;
}
return `<div class="menuwrap"><button class="iconbtn dotsbtn" title="Radåtgärder">${icon.dots}</button><div class="menu">${compact}${isSB()?'':''}<div class="sep"></div><button class="mi danger">${icon.trash} Ta bort rad</button></div></div>`;
}
const FIELDS=()=>[
{lab:'Artikel',w:120,al:'left',ctrl:r=>sel(ARTICLES,r.art)},
{lab:'Antal',w:60,al:'right',ctrl:r=>`<input class="num" value="${r.qty}">`},
{lab:'Enhet',w:78,al:'left',ctrl:r=>sel(UNITS,r.unit)},
{lab:'à-pris',w:96,al:'right',ctrl:r=>`<input class="num" value="${r.price}">`},
...(S.vat?[{lab:'Moms',w:70,al:'left',ctrl:r=>sel(VAT,r.vat)}]:[]),
];
function headerRow(){
const eco=FIELDS().map(f=>`<div class="f" style="width:${f.w}px"><span class="hlab" style="text-align:${f.al}">${f.lab}</span></div>`).join('');
return `<div class="srow headrow">
<div class="sgrip"></div>
<div class="sdesc"><span class="hlab">Beskrivning</span></div>
<div class="seco">${eco}</div>
<div class="sbel"><span class="hlab">Belopp</span></div>
<div class="sact"></div>
</div>`;
}
function bodyRow(r,actStyle){
const eco=FIELDS().map(f=>`<div class="f" style="width:${f.w}px">${f.ctrl(r)}</div>`).join('');
return `<div class="srow line">
<div class="sgrip">⠿</div>
<div class="sdesc"><input value="${r.desc}" placeholder="Beskrivning">${r.rot?rotStrip():''}</div>
<div class="seco">${eco}</div>
<div class="sbel">${r.tot}</div>
<div class="sact ${actStyle==='menu'?'keep':''}">${rowActions(actStyle)}</div>
</div>`;
}
function textRow(actStyle){
if(isSB())return '';
return `<div class="srow line">
<div class="sgrip">⠿</div>
<div class="sdesc"><input value="Tack för ditt förtroende." style="font-style:italic;color:var(--muted)" placeholder="Fritext (bokförs inte)"></div>
<div class="seco"></div><div class="sbel"></div>
<div class="sact ${actStyle==='menu'?'keep':''}">${rowActions(actStyle)}</div>
</div>`;
}
/* add affordance: 'ghost' = inline ghost rows ; 'buttons' = outline buttons below */
function addAffordance(style){
if(style==='ghost'){
return `<div class="ghostrow"><button class="ghostadd">${icon.plus} Lägg till rad</button></div>${isSB()?'':`<div class="ghostrow"><button class="ghostadd">${icon.plus} Lägg till textrad</button></div>`}`;
}
return `<div class="addrow"><button class="btn outline sm">${icon.plus} Lägg till rad</button>${isSB()?'':`<button class="btn ghost sm">${icon.plus} Lägg till textrad</button>`}</div>`;
}
function frame(work){
const mc=(l,c)=>`<div class="ul-cell"><span class="ul-lab">${l}</span>${c}</div>`;
const meta=`${isSB()?'':mc('Typ',sel(['Faktura','Proforma','Följesedel']))}${mc('Valuta',sel(CUR))}${mc('Datum','<input type="date" value="2026-07-21">')}${mc('Förfaller','<input type="date" value="2026-08-20">')}
<div class="menuwrap"><button class="pill dotsbtn">${icon.sliders} Referenser &amp; mer ${icon.chev}</button>
<div class="menu pop"><div style="display:flex;flex-direction:column;gap:14px">
${isSB()?`<div><label>Avtalsreferens (självfakturering)</label><input placeholder="SB-2026-014"></div>`:`<div class="grid2"><div><label>Er referens</label><input placeholder="Kundens ref"></div><div><label>Vår referens</label><input value="Emil Mattsson"></div></div>`}
${isSB()?'':`<div><div class="seclabel" style="margin-bottom:8px">ROT/RUT-uppgifter</div><div class="grid3"><div><label>Fastighetsbet.</label><input placeholder="Gårda 12:3"></div><div><label>Lgh.nr</label><input placeholder="1101"></div><div><label>BRF org.nr</label><input placeholder="769600-0000"></div></div></div>`}
<div><label>Anteckningar</label><textarea>Betalning inom 30 dagar. Dröjsmålsränta enligt räntelagen.</textarea></div>
</div></div></div>`;
const send=isSB()?'':`<button class="btn secondary">${icon.send} Skicka</button>`;
return `<div class="d3top"><h2>${isSB()?'Ny självfaktura':'Ny faktura'}</h2>
<div class="tabs"><button class="${!isSB()?'on':''}" onclick="setMode('invoice')">Faktura</button><button class="${isSB()?'on':''}" onclick="setMode('self_billed')">Självfaktura</button></div></div>
<div class="d3band">
<div style="flex:1;min-width:0;max-width:340px"><span class="ul-lab">${isSB()?'Utställare':'Kund'}</span>
<div style="display:flex;gap:8px;align-items:center"><div style="flex:1">${sel(['Välj kund…',...CUSTOMERS],'Nordisk Design AB')}</div><button class="btn ghost sm">${icon.plus}</button></div></div>
<div style="display:flex;gap:20px;align-items:flex-end;flex-wrap:wrap">${meta}</div>
</div>
${isSB()?'':`<div class="warn">${icon.warn}<div>Inga bankuppgifter inlagda ännu. Fakturan kan skapas men saknar betalinfo.</div></div>`}
${work}
<div class="sumbar"><div class="brk"><span>Delsumma 28 080,00</span>${S.vat?'<span>Moms 7 020,00</span>':''}<span>ROT 1 440,00</span></div>
<div class="pay"><span class="k">Att betala</span><span class="v">${S.vat?'33 660,00':'26 640,00'} kr</span></div>
<div class="acts"><button class="btn ghost">Spara utkast</button>${send}<button class="btn primary">${isSB()?'Registrera självfaktura':'Granska &amp; skapa'}</button></div>
</div>`;
}
function build(addStyle,actStyle){
const rows=ROWS.map(r=>bodyRow(r,actStyle)).join('')+textRow(actStyle);
const listCls=actStyle==='inline'?'list hovacts':'list';
const work=`<div class="work"><div class="seclabel" style="margin-bottom:6px">Fakturarader</div>
${headerRow()}
<div class="${listCls}">${rows}</div>
${addAffordance(addStyle)}
</div>`;
return frame(work);
}
const VARIANTS=[
{k:'F1',n:'Spökrad + ⋮',r:()=>build('ghost','menu')},
{k:'F2',n:'Knappar + ⋮',r:()=>build('buttons','menu')},
{k:'F3',n:'Spökrad + hover-åtgärder',r:()=>build('ghost','inline')},
{k:'F4',n:'Knappar + hover-åtgärder',r:()=>build('buttons','inline')},
];
const mount=document.getElementById('mount');
function render(){const V=VARIANTS[S.vi];mount.innerHTML=V.r();document.getElementById('curk').textContent=V.k;document.getElementById('curn').textContent=V.n;wire();}
function setMode(m){S.mode=m;document.querySelectorAll('#segMode button').forEach(b=>b.classList.toggle('on',b.dataset.m===m));render();}
function step(d){S.vi=(S.vi+d+VARIANTS.length)%VARIANTS.length;render();}
document.getElementById('prev').onclick=()=>step(-1);document.getElementById('next').onclick=()=>step(1);
document.querySelectorAll('#segMode button').forEach(b=>b.onclick=()=>setMode(b.dataset.m));
document.querySelectorAll('#segVat button').forEach(b=>b.onclick=()=>{S.vat=b.dataset.v==='1';document.querySelectorAll('#segVat button').forEach(x=>x.classList.toggle('on',x===b));render();});
document.addEventListener('keydown',e=>{const t=e.target.tagName;if(t==='INPUT'||t==='SELECT'||t==='TEXTAREA'||e.target.isContentEditable)return;if(e.key==='ArrowLeft')step(-1);if(e.key==='ArrowRight')step(1);});
function wire(){document.querySelectorAll('.dotsbtn').forEach(btn=>btn.onclick=e=>{e.stopPropagation();const m=btn.parentElement.querySelector('.menu');const open=m.classList.contains('open');closeMenus();m.classList.toggle('open',!open);const s=btn.closest('.sact');if(s)s.classList.toggle('keep',!open);});}
function closeMenus(){document.querySelectorAll('.menu.open').forEach(m=>m.classList.remove('open'));document.querySelectorAll('.sact.keep').forEach(s=>{if(!s.querySelector('.rowActions'))return;});}
document.addEventListener('click',()=>{document.querySelectorAll('.menu.open').forEach(m=>m.classList.remove('open'));});
(function(){const p=document.getElementById('picker'),h=document.getElementById('pgrip');let dx,dy,drag=false;
h.addEventListener('pointerdown',e=>{drag=true;const r=p.getBoundingClientRect();dx=e.clientX-r.left;dy=e.clientY-r.top;h.setPointerCapture(e.pointerId)});
h.addEventListener('pointermove',e=>{if(!drag)return;p.style.left=(e.clientX-dx)+'px';p.style.top=(e.clientY-dy)+'px';p.style.right='auto';p.style.bottom='auto'});
h.addEventListener('pointerup',()=>drag=false);})();
render();
</script>
</body>
</html>