Files
accounted/prototypes/invoice-create-redesign.html
T
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

306 lines
25 KiB
HTML
Raw 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>PROTOTYP — Kundfaktura redesign (round 9: back to clean editorial, ALL fields)</title>
<!--
THROWAWAY PROTOTYPE — /grilling-frontend-prototyping.
ROUND 9 — back to the clean editorial language of rounds 2-3 (which Emil
liked), now carrying EVERY field. No new borders, no nested boxes, no chips,
no emoji, no ⋮ clutter. Advanced per-line bits (ROT/RUT, periodisering, konto)
render as a quiet indented line with one thin rule. Three early favourites,
all complete: Faktabladet, Rubrik, Stegduk. Opens on Fullt to verify nothing
is missing.
-->
<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=Inter:wght@400;450;500;600&display=swap" rel="stylesheet">
<style>
:root{
--bg:#ffffff; --fg:#1c1b19; --muted:#6f6b64; --faint:#a19c92;
--secondary-60:#f5f1e9; --border:#e3ded4; --border-strong:#cfc9bd;
--primary:#1c1b19; --primary-fg:#faf9f6;
--sans:'Inter',system-ui,-apple-system,Segoe UI,sans-serif; --serif:'Hedvig Letters Serif',Georgia,serif;
}
*{box-sizing:border-box} html,body{margin:0;height:100%}
body{background:#fff;color:var(--fg);font-family:var(--sans);font-size:14px;line-height:1.55;-webkit-font-smoothing:antialiased}
.tnum{font-variant-numeric:tabular-nums} .serif{font-family:var(--serif);font-weight:400;letter-spacing:-0.012em}
.muted{color:var(--muted)} .faint{color:var(--faint)} button{font-family:inherit}
.app{display:grid;grid-template-columns:248px 1fr;height:100vh;overflow:hidden}
.side{border-right:1px solid var(--border);padding:22px 16px;display:flex;flex-direction:column;gap:2px;background:#fff}
.brand{font-family:var(--serif);font-size:20px;letter-spacing:-0.02em;padding:6px 10px 20px}
.nav a{display:flex;align-items:center;gap:11px;padding:8px 10px;border-radius:8px;color:var(--muted);text-decoration:none;font-size:13.5px}
.nav a .dotnav{width:5px;height:5px;border-radius:50%;background:var(--border-strong)}
.nav a.active{background:#f1ede5;color:var(--fg)} .nav a.active .dotnav{background:var(--fg)}
.nav a:hover:not(.active){background:#f7f4ee} .side .spacer{flex:1}
.who{display:flex;align-items:center;gap:10px;padding:10px;border-top:1px solid var(--border);margin-top:8px}
.avatar{width:26px;height:26px;border-radius:50%;background:#eae5db;display:grid;place-items:center;font-size:11px;color:var(--muted)}
.main{overflow:auto;position:relative} .stage{min-height:100%;background:#faf8f4}
.inp,select.inp{width:100%;border:1px solid var(--border-strong);border-radius:8px;background:#fff;height:40px;padding:0 12px;font-family:inherit;font-size:14px;color:var(--fg)}
select.inp{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='%23a19c92' stroke-width='2'><path d='M6 9l6 6 6-6'/></svg>");background-repeat:no-repeat;background-position:right 11px center;padding-right:30px;cursor:pointer}
.inp:focus,select.inp:focus{outline:0;border-color:var(--fg);box-shadow:0 0 0 3px rgba(28,27,25,.08)}
.inp::placeholder{color:var(--faint)} textarea.inp{height:auto;min-height:56px;padding:10px 12px;resize:vertical;line-height:1.55}
.lbl{display:block;font-size:12px;color:var(--muted);margin-bottom:6px} .req{color:#b4533f;margin-left:2px}
.btn{display:inline-flex;align-items:center;justify-content:center;height:44px;padding:0 22px;border-radius:9px;border:0;background:var(--primary);color:var(--primary-fg);font-size:14px;font-weight:500;cursor:pointer} .btn:hover{background:#322e26}
.obtn{display:inline-flex;align-items:center;justify-content:center;height:44px;padding:0 18px;border-radius:9px;border:1px solid var(--border-strong);background:#fff;color:var(--fg);font-size:14px;cursor:pointer} .obtn:hover{background:#f7f4ee}
.tbtn{background:0;border:0;color:var(--muted);font-size:13.5px;cursor:pointer;padding:8px 0} .tbtn:hover{color:var(--fg)}
.doclabel{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
.surface{margin:0 auto;background:#fff;border:1px solid var(--border);border-radius:14px}
.wrap{padding:40px 24px 60px}
.sech{font-family:var(--serif);font-size:16px}
.metagrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:18px 22px}
.tabs{display:flex;gap:22px} .tabs button{background:0;border:0;padding:5px 0;font-size:14px;color:var(--muted);cursor:pointer;border-bottom:1.5px solid transparent} .tabs button.on{color:var(--fg);border-bottom-color:var(--fg)}
/* clean line list (borderless cells, hairline rows) */
.lg{width:100%;border-collapse:collapse}
.lg th{font-weight:400;color:var(--faint);font-size:11px;text-align:left;padding:0 12px 10px 0}
.lg thead tr{border-bottom:1px solid var(--border)} .lg th.r,.lg td.r{text-align:right}
.lg td{padding:3px 12px 3px 0;border:0;vertical-align:middle} .lg tbody tr.l{border-bottom:1px solid var(--border)}
.lg .c{border:0;background:transparent;width:100%;padding:11px 0;font-family:inherit;font-size:14px;color:var(--fg)}
.lg .c.r{text-align:right;font-variant-numeric:tabular-nums} .lg .c:focus{outline:0;box-shadow:0 -2px 0 var(--fg) inset}
.lg .amt{padding:11px 0;font-variant-numeric:tabular-nums;font-weight:600;white-space:nowrap}
.lg .rm{opacity:0;color:var(--faint);cursor:pointer;font-size:15px} .lg tr.l:hover .rm{opacity:1}
.art{font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:var(--faint);margin-right:8px}
/* quiet advanced line: one thin left rule, no box/chip/emoji */
.adv{margin:0 0 4px;padding:6px 0 12px 14px;border-left:2px solid var(--border);display:flex;flex-wrap:wrap;align-items:flex-end;gap:14px 20px}
.advk{font-size:12px;color:var(--muted);font-weight:500;align-self:center}
.micro{display:block;font-size:11px;color:var(--faint);margin-bottom:3px}
.ins,select.ins{border:1px solid var(--border-strong);border-radius:7px;height:34px;padding:0 10px;font-size:13px;background:#fff;font-family:inherit;color:var(--fg)}
select.ins{appearance:none;padding-right:24px;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 24 24' fill='none' stroke='%23a19c92' stroke-width='2'><path d='M6 9l6 6 6-6'/></svg>");background-repeat:no-repeat;background-position:right 8px center}
.advamt{font-size:12.5px;color:var(--muted);align-self:center} .advnote{width:100%;font-size:12px;color:var(--muted)}
.addrow{display:flex;gap:20px;margin-top:14px} .addrow .tbtn{padding-top:4px}
.sums{width:290px}
.sumrow{display:flex;justify-content:space-between;padding:5px 0;font-size:13.5px}
.sumtot{display:flex;justify-content:space-between;align-items:baseline;margin-top:10px;padding-top:14px;border-top:1px solid var(--border)} .sumtot .val{font-family:var(--serif);font-size:27px}
.sw{width:38px;height:22px;border-radius:999px;background:var(--border-strong);position:relative;cursor:pointer;flex:none} .sw.on{background:var(--fg)} .sw::after{content:'';position:absolute;width:18px;height:18px;border-radius:50%;background:#fff;top:2px;left:2px;transition:.15s} .sw.on::after{left:18px}
.step{padding:24px 34px;border-top:1px solid var(--border)} .stephead{display:flex;align-items:center;gap:10px;margin-bottom:16px}
.stepnum{width:22px;height:22px;border-radius:50%;background:var(--secondary-60);display:grid;place-items:center;font-size:12px;font-variant-numeric:tabular-nums}
.picker{position:fixed;right:22px;bottom:22px;width:344px;z-index:9999;background:#141311;color:#f4f0e8;border-radius:12px;box-shadow:0 10px 40px rgba(0,0,0,.34);font-size:13px;user-select:none}
.picker-h{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:grab;border-bottom:1px solid #2a2823} .picker-h .dot{width:8px;height:8px;border-radius:50%;background:#c9a24a} .picker-h b{font-weight:600}
.picker-b{padding:12px} .picker-nav{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.picker-nav button{width:34px;height:34px;border-radius:8px;border:1px solid #35322b;background:#211f1b;color:#f4f0e8;font-size:16px;cursor:pointer;display:grid;place-items:center} .picker-nav button:hover{background:#2c2924}
.picker-name{flex:1;text-align:center} .picker-name .n{font-family:var(--serif);font-size:16px} .picker-name .k{font-size:11px;color:#a49d8d;letter-spacing:.08em}
.picker-desc{color:#b8b1a1;font-size:12px;line-height:1.45;min-height:60px}
.picker-states{display:flex;gap:6px;margin-top:10px;border-top:1px solid #2a2823;padding-top:10px}
.picker-states button{flex:1;height:30px;border-radius:7px;border:1px solid #35322b;background:#211f1b;color:#cdc7b8;cursor:pointer;font-size:12px}
.picker-states button.on{background:#f4f0e8;color:#141311;border-color:#f4f0e8;font-weight:600} .picker-count{font-size:11px;color:#8f887a;margin-left:auto}
</style>
</head>
<body>
<div class="app">
<aside class="side">
<div class="brand">Accounted</div>
<nav class="nav">
<a href="#"><span class="dotnav"></span>Översikt</a>
<a href="#" class="active"><span class="dotnav"></span>Fakturor</a>
<a href="#"><span class="dotnav"></span>Kvitton</a>
<a href="#"><span class="dotnav"></span>Bokföring</a>
<a href="#"><span class="dotnav"></span>Rapporter</a>
<a href="#"><span class="dotnav"></span>Moms &amp; skatt</a>
<a href="#"><span class="dotnav"></span>Inställningar</a>
</nav>
<div class="spacer"></div>
<div class="who"><div class="avatar">KS</div><div style="font-size:12.5px"><div>Kvist Studio AB</div><div class="faint" style="font-size:11px">emil@kviststudio.se</div></div></div>
</aside>
<main class="main"><div class="stage" id="stage"></div></main>
</div>
<div class="picker" id="picker">
<div class="picker-h" id="pickerHandle"><span class="dot"></span><b>Redesign</b> · rond 9 · ren + alla fält<span class="picker-count" id="pcount"></span></div>
<div class="picker-b">
<div class="picker-nav">
<button id="prev" aria-label="Föregående"></button>
<div class="picker-name"><div class="n" id="pname"></div><div class="k" id="pkey"></div></div>
<button id="next" aria-label="Nästa"></button>
</div>
<div class="picker-desc" id="pdesc"></div>
<div class="picker-states">
<button data-state="empty">Tom</button>
<button data-state="filled">Ifylld</button>
<button data-state="full" class="on">Fullt</button>
</div>
</div>
</div>
<script>
const money=(n,dec=false)=>(n).toLocaleString('sv-SE',dec?{minimumFractionDigits:2,maximumFractionDigits:2}:{})+' kr';
const amt=n=>Number.isInteger(n)?money(n):money(n,true);
const CUST={name:'Björklund Design AB',org:'556677-8899',addr:'Kungsgatan 12, 111 43 Stockholm',ref:'Anna Björklund'};
const SIMPLE=[{desc:'Webbutveckling, sprint 3',qty:24,unit:'tim',price:950,vat:25},{desc:'UX-rådgivning',qty:6,unit:'tim',price:1200,vat:25}];
const FULLROWS=[
{art:'A102',desc:'Webbutveckling, sprint 3',qty:24,unit:'tim',price:950,vat:25,rot:true},
{desc:'UX-rådgivning',qty:6,unit:'tim',price:1200,vat:25,accrual:true},
{desc:'Serverdrift, 12 mån',qty:1,unit:'st',price:2999.60,vat:25,konto:true},
{textRow:true,desc:'Allt arbete är utfört enligt avtal 2026-05-14.'},
];
const TT={empty:{sub:0,vat:0,pay:0},filled:{sub:30000,vat:7500,pay:37500},full:{sub:32999.60,vat:8249.90,pay:34410,rot:6840,round:0.50,total:41249.50}};
const V=s=>s!=='empty';
function pv(p){return p===''?'':(Number.isInteger(p)?p:p.toLocaleString('sv-SE',{minimumFractionDigits:2,maximumFractionDigits:2}));}
function rowsFor(s){return s==='empty'?[{desc:'',qty:'',unit:'st',price:'',vat:25}]:(s==='full'?FULLROWS:SIMPLE);}
function advClean(l){
if(l.rot) return `<tr><td colspan="7" style="padding:0"><div class="adv"><span class="advk">ROT-avdrag 30 %</span>
<div><span class="micro">Arbetstyp</span><select class="ins"><option>Bygg och renovering</option></select></div>
<div><span class="micro">Timmar</span><input class="ins tnum" style="width:90px" value="24"></div>
<span class="advamt tnum">${money(6840,true)}</span>
<span class="advnote">Endast arbetskostnad ger skattereduktion, fakturera material på egen rad.</span></div></td></tr>`;
if(l.accrual) return `<tr><td colspan="7" style="padding:0"><div class="adv"><span class="advk">Periodisering</span>
<div><span class="micro">Från</span><input class="ins tnum" style="width:120px" value="2026-07-01"></div>
<div><span class="micro">Till</span><input class="ins tnum" style="width:120px" value="2026-12-31"></div>
<div><span class="micro">Balanskonto</span><select class="ins"><option>2979 Upplupna intäkter</option></select></div></div></td></tr>`;
if(l.konto) return `<tr><td colspan="7" style="padding:0"><div class="adv"><span class="advk">Försäljningskonto</span>
<div><select class="ins" style="min-width:240px"><option>3540 Fakturerade kostnader</option></select></div>
<span class="advnote" style="width:auto;align-self:center">ersätter det momssatshärledda kontot</span></div></td></tr>`;
return '';
}
function lineList(state){
const rows=rowsFor(state).map(l=>{
if(l.textRow) return `<tr class="l"><td colspan="6"><input class="c" style="color:var(--muted);font-style:italic" value="${l.desc}" placeholder="Fritext, notering på raden"></td><td class="r"><span class="rm">×</span></td></tr>`;
const has=l.qty!==''&&l.price!==''; const lt=has?l.qty*l.price:0;
return `<tr class="l">
<td><div style="display:flex;align-items:center">${l.art?`<span class="art">${l.art}</span>`:''}<input class="c" value="${l.desc}" placeholder="Beskrivning"></div></td>
<td style="width:60px" class="r"><input class="c r" value="${l.qty}" placeholder="1"></td>
<td style="width:56px"><input class="c" value="${l.unit}"></td>
<td style="width:96px" class="r"><input class="c r" value="${pv(l.price)}" placeholder="0"></td>
<td style="width:54px" class="r"><input class="c r" value="${l.vat}%"></td>
<td style="width:116px" class="r amt">${has?amt(lt):'—'}</td>
<td style="width:20px" class="r"><span class="rm">×</span></td>
</tr>${state==='full'?advClean(l):''}`;
}).join('');
const addAdv = state==='full'?'':'<button class="tbtn" style="color:var(--faint)">Skattereduktion / periodisering</button>';
return `<table class="lg"><thead><tr><th>Beskrivning</th><th class="r">Antal</th><th>Enhet</th><th class="r">À-pris</th><th class="r">Moms</th><th class="r">Belopp</th><th></th></tr></thead><tbody>${rows}</tbody></table>
<div class="addrow"><button class="tbtn">+ Lägg till rad</button><button class="tbtn">Välj artikel</button><button class="tbtn">Fritext</button>${addAdv}</div>`;
}
function metaGrid(state){const v=V(state);return `<div class="metagrid">
<div><span class="lbl">Fakturadatum<span class="req">*</span></span><input class="inp tnum" value="${v?'2026-07-21':''}" placeholder="åååå-mm-dd"></div>
<div><span class="lbl">Förfallodatum<span class="req">*</span></span><input class="inp tnum" value="${v?'2026-08-20':''}" placeholder="+30 dagar"></div>
<div><span class="lbl">Leveransdatum</span><input class="inp tnum" value="${state==='full'?'2026-07-18':''}" placeholder="valfritt"></div>
<div><span class="lbl">Valuta</span><select class="inp"><option>SEK</option><option>EUR</option><option>USD</option></select></div>
<div><span class="lbl">Er referens</span><input class="inp" value="${v?CUST.ref:''}" placeholder="kontaktperson"></div>
<div><span class="lbl">Vår referens</span><input class="inp" value="${v?'Emil Mattsson':''}" placeholder="du"></div>
<div><span class="lbl">Betallänk</span><input class="inp" value="${state==='full'?'buy.stripe.com/…':''}" placeholder="https://…"></div>
<div><span class="lbl">Kostnadsställe</span><input class="inp" value="${state==='full'?'Konsult':''}" placeholder="valfritt"></div>
<div><span class="lbl">Projekt</span><input class="inp" value="${state==='full'?'Webb 2026':''}" placeholder="valfritt"></div>
</div>`;}
function rotClaim(state){
if(state!=='full') return '';
return `<div style="margin-top:8px"><div class="sech" style="margin-bottom:14px">Skattereduktion <span class="muted" style="font-size:12.5px">· uppgifter för Skatteverket (ROT)</span></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px 22px">
<div><span class="lbl">Personnummer (köpare)<span class="req">*</span></span><input class="inp" placeholder="ÅÅÅÅMMDD-XXXX"></div>
<div><span class="lbl">Fastighetsbeteckning<span class="req">*</span></span><input class="inp" value="Stockholm Väsby 1:23"></div>
</div></div>`;
}
function notes(state){return `<textarea class="inp" rows="2" placeholder="Valfri text som visas på fakturan">${state==='empty'?'':'Tack för förtroendet. Betalning sker via bankgiro 123-4567 senast på förfallodagen.'}</textarea>`;}
function summary(state){
const d=TT[state]; const m=n=>state==='full'?money(n,true):money(n);
return `<div class="sums">
<div class="sumrow"><span class="muted">Delsumma</span><span class="tnum">${m(d.sub)}</span></div>
<div class="sumrow"><span class="muted">Moms 25 %</span><span class="tnum">${m(d.vat)}</span></div>
${d.round?`<div class="sumrow"><span class="muted">Öresavrundning</span><span class="tnum">+${money(d.round,true)}</span></div>`:''}
${d.rot?`<div class="sumrow"><span class="muted">ROT-avdrag</span><span class="tnum">${money(d.rot,true)}</span></div>`:''}
<div class="sumtot"><span>Att betala</span><span class="val tnum">${money(d.pay)}</span></div>
${d.rot?`<div class="sumrow" style="margin-top:6px"><span class="muted">Total inkl. moms</span><span class="tnum">${money(d.total,true)}</span></div>`:''}
<div class="sumrow" style="margin-top:14px;align-items:center"><span class="muted">Öresavrundning</span><span class="sw ${V(state)?'on':''}"></span></div>
</div>`;
}
const actions=`<div style="display:flex;gap:12px;align-items:center"><button class="btn">Granska &amp; skapa</button><button class="obtn">Spara utkast</button><button class="tbtn">Förhandsgranska</button></div>`;
const custBlock=(state)=> state==='empty'
? `<input class="inp" style="height:44px;max-width:440px;font-size:15px" placeholder="Sök kund, eller skapa ny">`
: `<div style="display:flex;justify-content:space-between;align-items:flex-start"><div><div style="font-weight:500;font-size:15px">${CUST.name}</div><div class="muted" style="font-size:12.5px">${CUST.org} · ${CUST.addr}</div></div><button class="tbtn">Byt kund</button></div>`;
/* A — Faktabladet (letterhead) */
function vLetter(state){
return `<div class="wrap"><div class="surface" style="max-width:880px;padding:44px 48px 40px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:30px">
<div class="tabs"><button class="on">Faktura</button><button>Proforma</button><button>Följesedel</button></div>
</div>
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:34px">
<div><div class="serif" style="font-size:21px">Kvist Studio AB</div><div class="muted" style="margin-top:4px;font-size:12.5px">Hornsgatan 1, 118 20 Stockholm · Org 559111-2233</div></div>
<div style="text-align:right"><div class="doclabel" style="letter-spacing:.22em">Faktura</div><div class="serif tnum" style="font-size:26px">F2026-014</div></div>
</div>
<div style="margin-bottom:26px"><div class="doclabel" style="margin-bottom:10px">Faktureras till</div>${custBlock(state)}</div>
<div style="margin-bottom:30px">${metaGrid(state)}</div>
<div class="sech" style="margin-bottom:12px">Rader</div>
${lineList(state)}
${state==='full'?`<div style="margin-top:26px">${rotClaim(state)}</div>`:''}
<div style="display:grid;grid-template-columns:1fr 300px;gap:36px;margin-top:32px">
<div><div class="sech" style="margin-bottom:12px">Anteckningar</div>${notes(state)}</div>
<div>${summary(state)}</div>
</div>
<hr style="border:0;border-top:1px solid var(--border);margin:28px 0 20px">${actions}
</div></div>`;
}
/* B — Rubrik (editorial title) */
function vTitle(state){
return `<div class="wrap"><div class="surface" style="max-width:880px;padding:0">
<div style="padding:30px 40px 24px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:flex-end">
<div><div class="doclabel" style="margin-bottom:8px">Faktura F2026-014</div>
${V(state)?`<div class="serif" style="font-size:28px">${CUST.name}</div><div class="muted" style="font-size:12.5px;margin-top:2px">${CUST.org} · ${CUST.addr}</div>`:`<div class="serif" style="font-size:28px;color:var(--faint)">Ny faktura</div>`}</div>
<div class="tabs" style="padding-bottom:4px"><button class="on">Faktura</button><button>Proforma</button><button>Följesedel</button></div>
</div>
${state==='empty'?`<div style="padding:20px 40px 0">${custBlock(state)}</div>`:''}
<div style="padding:24px 40px 0"><div class="doclabel" style="margin-bottom:14px">Detaljer</div>${metaGrid(state)}</div>
<div style="padding:24px 40px 0"><div class="sech" style="margin-bottom:12px">Rader</div>${lineList(state)}</div>
${state==='full'?`<div style="padding:26px 40px 0">${rotClaim(state)}</div>`:''}
<div style="padding:26px 40px 0"><div class="sech" style="margin-bottom:12px">Anteckningar</div>${notes(state)}</div>
<div style="padding:28px 40px 34px;margin-top:20px;border-top:1px solid var(--border);display:flex;justify-content:space-between;align-items:flex-end;gap:32px">
<div>${actions}</div><div>${summary(state)}</div>
</div>
</div></div>`;
}
/* C — Stegduk (numbered steps) */
function vSteps(state){
const step=(n,label,body,hint='')=>`<div class="step"><div class="stephead"><span class="stepnum">${n}</span><span class="sech">${label}</span>${hint?`<span class="muted" style="font-size:12px">${hint}</span>`:''}</div>${body}</div>`;
return `<div class="wrap"><div class="surface" style="max-width:820px;padding:0">
<div style="padding:24px 34px 8px;display:flex;justify-content:space-between;align-items:center"><div class="doclabel">Ny kundfaktura · F2026-014</div>
<div class="tabs"><button class="on">Faktura</button><button>Proforma</button><button>Följesedel</button></div></div>
${step(1,'Kund', custBlock(state))}
${step(2,'Rader', lineList(state))}
${step(3,'Villkor', metaGrid(state))}
${step(4,'Skattereduktion', state==='full'?rotClaim(state).replace('<div style="margin-top:8px">','<div>'):`<div class="muted" style="font-size:12.5px">Inga ROT/RUT-avdrag. Läggs till per rad under Rader.</div>`, state==='full'?'':'valfritt')}
${step(5,'Anteckningar', notes(state),'valfritt')}
<div class="step" style="display:flex;justify-content:space-between;align-items:flex-end;gap:32px"><div>${actions}</div><div>${summary(state)}</div></div>
</div></div>`;
}
const VARIANTS=[
{ key:'faktabladet', name:'Faktabladet', render:vLetter,
desc:'Fakturablad med brevhuvud: logga/org uppe, nr till höger, kund, sedan villkoren i ett luftigt fältrutnät, rader, summa. Alla fält, dokumentkänsla.' },
{ key:'rubrik', name:'Rubrik', render:vTitle,
desc:'Kundnamnet som stor serif-rubrik, all meta i en lugn "Detaljer"-yta, rena rader. Redaktionellt och lätt att läsa. Håller allt utan att kännas fullt.' },
{ key:'stegduk', name:'Stegduk', render:vSteps,
desc:'Numrerade steg: Kund → Rader → Villkor → Skattereduktion → Anteckningar → Summa. Varje fält får sin plats, inget trängs. Tydligast.' },
];
let idx=0, state='full';
const stage=document.getElementById('stage');
function draw(){
const v=VARIANTS[idx];
stage.innerHTML=v.render(state);
document.getElementById('pname').textContent=v.name;
document.getElementById('pkey').textContent=v.key.toUpperCase();
document.getElementById('pdesc').textContent=v.desc;
document.getElementById('pcount').textContent=` ${idx+1}/${VARIANTS.length}`;
stage.parentElement.scrollTop=0;
}
function go(d){ idx=(idx+d+VARIANTS.length)%VARIANTS.length; draw(); }
document.getElementById('prev').onclick=()=>go(-1);
document.getElementById('next').onclick=()=>go(1);
document.querySelectorAll('.picker-states button').forEach(b=>{ b.onclick=()=>{ state=b.dataset.state; document.querySelectorAll('.picker-states button').forEach(x=>x.classList.toggle('on',x===b)); draw(); }; });
document.addEventListener('keydown',e=>{ const t=e.target.tagName; if(t==='INPUT'||t==='TEXTAREA'||t==='SELECT'||e.target.isContentEditable) return; if(e.key==='ArrowLeft') go(-1); if(e.key==='ArrowRight') go(1); });
(function(){ const p=document.getElementById('picker'),h=document.getElementById('pickerHandle'); let dx=0,dy=0,drag=false;
h.addEventListener('mousedown',e=>{drag=true;dx=e.clientX-p.offsetLeft;dy=e.clientY-p.offsetTop;p.style.right='auto';p.style.bottom='auto';h.style.cursor='grabbing';});
document.addEventListener('mousemove',e=>{if(!drag)return;p.style.left=(e.clientX-dx)+'px';p.style.top=(e.clientY-dy)+'px';});
document.addEventListener('mouseup',()=>{drag=false;h.style.cursor='grab';}); })();
draw();
</script>
</body>
</html>