/* ==========================================================================
   Nordisk Solteknik — implementation of "Nordisk Solteknik.dc.html"
   Design canvas: desktop artboard 1440 / mobile artboard 390.
   Type and spacing interpolate linearly between those two widths so the
   rendered values match the canvas exactly at 390px and at 1180px+.
   ========================================================================== */

/* --- Fonts ---------------------------------------------------------------- */
@font-face{font-family:"Geist";font-style:normal;font-weight:400 700;font-display:swap;src:url("../assets/fonts/geist-latin.woff2") format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"Geist";font-style:normal;font-weight:400 700;font-display:swap;src:url("../assets/fonts/geist-latin-ext.woff2") format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}

/* --- Tokens --------------------------------------------------------------- */
:root{
  /* colour */
  /* ── Colour ───────────────────────────────────────────────────────────
     Anchored on the logo: #024D68 hsl(196 96% 21%) and #FFC000 hsl(45 100% 50%).
     The blue scale is one hue (196°) held constant so every tint reads as the
     same colour at different strengths. Neutrals carry a slight cool cast so
     they sit with the blue instead of fighting it.

     Distribution target — 60 / 30 / 10:
       60%  neutral grounds  (--paper, --surface)
       30%  blue family      (tints for large areas, deep for punctuation)
       10%  amber            (CTAs, selection, one accent band per page)

     Six grounds, each one step apart, so two in sequence never read as one
     block. Light: paper → surface → tint. Dark: blue → dark → deep.
     Every page walks down that ladder and closes on the deepest.
     ──────────────────────────────────────────────────────────────────────── */
  --brand-900:#012C3C;
  --brand-800:#023F55;
  --brand:#024D68;          /* logo blue */
  --brand-600:#036487;
  --brand-500:#037DAA;
  --brand-300:#3CB2DD;
  --brand-100:#D3E7EE;
  --brand-50:#E7F1F6;
  --brand-25:#F3F8FA;

  --accent:#FFC000;         /* logo amber */
  --accent-hover:#E8AE00;
  --accent-50:#FFF6DC;
  /* The amber is never painted flat. It is treated as a lit surface: a sunlit
     side and a shaded side of the same logo yellow, so a yellow area reads as
     light falling on something rather than as a slab of ink. */
  --accent-lit:#FFD75E;
  --accent-shade:#F2A900;
  /* Unused since the buttons were flattened — kept because putting the
     gradient face back is then a one-line change in .btn--accent. */
  --accent-face:linear-gradient(158deg,var(--accent-lit) 0%,var(--accent) 46%,var(--accent-shade) 100%);
  --accent-face-hover:linear-gradient(158deg,var(--accent) 0%,var(--accent-hover) 46%,#D89800 100%);

  --ink:#0E1A20;
  --ink-muted:#46585F;
  --ink-faint:#7C8E96;
  --hair:#DCE5E8;
  --hair-strong:#C7D5DA;
  --surface:#F1F5F7;
  --surface-raised:#FFFFFF;
  --surface-sunken:#E4EBEE;

  /* deep grounds are brand blue now, not near-black — the site reads light */
  --dark:var(--brand-800);
  --dark-fg:#EAF3F7;
  --dark-body:#C9DEE7;
  --dark-hair:#0A5A75;
  --dark-border:#10688A;

  --blue:var(--brand);      /* alias kept for existing figure/link usages */

  /* type */
  /* Geist, på Julius besked. Var Familjen Grotesk. Självvärdad som förut:
     netlify.toml sätter font-src 'self', så ett CDN-anrop hade blockerats av
     CSP:n och sidan tyst fallit tillbaka på system-ui.
     Variabel vikt 400-700 i samma två snitt, latin och latin-ext, med samma
     unicode-range som förut. Ext-snittet bär bland annat de tecken som
     tidigare låg i det gamla, och åäö ligger i latin-snittet. */
  --sans:"Geist",system-ui,-apple-system,sans-serif;
  /* --fig hette --mono och var IBM Plex Mono. Borttagen på Julius besked: han
     ville inte se den någonstans på sajten, sidfoten inräknad.

     Variabeln finns kvar, med nytt namn, för att den bar en ROLL och inte ett
     typsnitt: siffror, mått, koder och bildtexter under en figur. Femtiosju
     regler pekade på den. Att peka om dem till --sans en och en hade varit
     samma ändring utspridd på femtiosju ställen, och nästa gång rollen ska
     byta utseende hade den inte funnits kvar att byta på.

     Namnet fick inte stå kvar som --mono. En variabel som heter mono och inte
     är monospaced är en fälla för den som läser regeln och tror sig veta vad
     den ger.

     TABELLSIFFRORNA HÅLLER. Det var den enda verkliga risken: kolumnerna i
     lönsamhetskalkylen ligger på font-variant-numeric:tabular-nums, och med ett
     proportionellt typsnitt utan tabellsiffror hade de blivit ojämna. Mätt i
     webbläsaren: tio siffror i rad i Familjen Grotesk var exakt lika breda för
     alla tio tecknen, både med och utan tabular-nums.

     EFTER BYTET TILL GEIST GÄLLER DET INTE LÄNGRE UTAN VIDARE, och det är värt
     att veta. Geist har PROPORTIONELLA siffror i grunden: mätt på samma sätt
     skiljer tio ettor och tio nollor 129,6 px vid 40 px. Med tabular-nums är
     skillnaden noll. Kolumnerna håller alltså bara så länge regeln som sätter
     dem också sätter font-variant-numeric.
     Genomgånget vid bytet: tjugofyra av reglerna som använder --fig sätter
     tabular-nums, och det är de som står i kolumn — kalkylens rader,
     antagandelistan, jämförelsens tal. De trettiotvå övriga är ensamma tal och
     bildtexter, där siffrornas bredd inte har något att linjera mot.
     Lägger man en ny sifferkolumn måste tabular-nums med. */
  --fig:var(--sans);

  --fs-body:clamp(16px,calc(15.01px + 0.253vw),18px);      /* 16 → 18  */
  --fs-mono:clamp(13px,calc(12.51px + 0.127vw),14px);      /* 13 → 14  */
  --fs-mono-sm:clamp(12px,calc(11.51px + 0.127vw),13px);   /* 12 → 13  */
  /* Type scale taken from design-variation-1 (2026-07-31). The old ramp
     topped out at 67px, which made the headline the loudest thing on every
     page and left section headings competing with it. This one is short:
     54 hero, 48 page, 32 section, 26 sub. The drama moves to photography and
     to the space between blocks, which is what the reference set does.
     Interpolated 390 → 1180 like everything else in this file. */
  --fs-lead:clamp(18px,calc(15.04px + 0.759vw),24px);      /* 18 → 24  */
  --fs-h1:clamp(34px,calc(27.09px + 1.772vw),48px);        /* 34 → 48  */
  --fs-h1-hero:clamp(36px,calc(27.11px + 2.278vw),54px);   /* 36 → 54  */
  --fs-h2:clamp(26px,calc(23.04px + 0.759vw),32px);        /* 26 → 32  */
  --fs-h2-sm:clamp(22px,calc(20.03px + 0.506vw),26px);     /* 22 → 26  */
  --fs-h3:clamp(19px,calc(16.53px + 0.633vw),24px);        /* 19 → 24  */
  --fs-num-xl:clamp(34px,calc(25.11px + 2.278vw),52px);    /* 34 → 52  */
  --fs-num-lg:clamp(28px,calc(22.08px + 1.519vw),40px);    /* 28 → 40  */

  /* space */
  --gutter:clamp(20px,calc(6.18px + 3.544vw),48px);        /* 20 → 48  */
  /* Also from design-variation-1: every step down one notch, so a section
     breathes without the page becoming a scroll. Paired with the shorter
     type ramp above; they were tuned together and do not separate well. */
  --pad-xl:clamp(56px,calc(40.20px + 4.051vw),88px);       /* 56 → 88  */
  --pad-lg:clamp(48px,calc(36.15px + 3.038vw),72px);       /* 48 → 72  */
  --pad-md:clamp(40px,calc(28.15px + 3.038vw),64px);       /* 40 → 64  */
  --pad-sm:clamp(32px,calc(24.10px + 2.025vw),48px);       /* 32 → 48  */
  --card-pad:clamp(20px,calc(14.08px + 1.519vw),32px);     /* 20 → 32  */

  /* 1180 → 1220, the one container change taken from the variation. The
     radii there (32/24/14/pill) are deliberately NOT adopted: the corner
     treatment is the part of that design Julius did not want. */
  --measure:1220px;
  --radius:8px;
  --radius-sm:4px;
}

/* --- Reset ---------------------------------------------------------------- */
*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  margin:0;
  background:var(--surface);
  color:var(--ink);
  font-family:var(--sans);
  font-size:var(--fs-body);
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
h1,h2,h3,h4,p,figure,dl,dd,ul,ol{margin:0}
ul,ol{list-style:none;padding:0}
/* height:auto hör ihop med max-width:100% och saknades. Utan den fortsätter
   HTML-attributet height att gälla som använd höjd medan max-width krymper
   bredden, och bilden dras ut. Det slog mot varje bild vars figur saknar egen
   regel: diagrammet i artikel-kvartspris.html är 1600x900 i filen och
   renderade 1124 brett men 900 högt, alltså ett liggande diagram pressat till
   stående. Regeln ligger först i filen, så varje senare regel med en verklig
   höjd (.shopcard__media img, .media-slot--photo img, .teampair__item img)
   vinner precis som förut. */
img,svg,iframe{display:block;max-width:100%;height:auto}
button,input,textarea{font:inherit;color:inherit}
a{color:inherit}
a:hover{color:var(--blue)}
/* …but not on a blue ground, where --blue on --brand-800/900 is unreadable. */
.utility a:hover,.hero a:hover,.section--blue a:hover,.section--dark a:hover,
.section--deep a:hover,.closer a:hover{color:#fff}
::selection{background:var(--accent);color:var(--ink)}
:focus-visible{outline:2px solid var(--blue);outline-offset:3px}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}
}

/* --- Layout primitives ---------------------------------------------------- */
.container{max-width:var(--measure);margin:0 auto;padding:0 var(--gutter)}
.section{padding:var(--pad-xl) 0}
.section--lg{padding:var(--pad-lg) 0}
.section--md{padding:var(--pad-md) 0}
.section--top{border-top:1px solid var(--hair)}
/* Grounds set background-COLOR, never the shorthand: the glow system paints
   background-image, and a shorthand anywhere later would silently wipe it. */
.section--white{background-color:var(--surface-raised)}
.section--dark{background-color:var(--dark);color:var(--dark-fg)}
.section--dark .section--top,.section--dark+.section--top{border-color:var(--dark-hair)}
.stack{display:flex;flex-direction:column}

/* --- Type ----------------------------------------------------------------- */
.eyebrow{
  display:block;
  font-family:var(--sans);
  font-size:15px;
  font-weight:500;
  color:var(--ink-muted);
}
.eyebrow--faint{color:var(--ink-faint)}
.eyebrow--light{color:var(--dark-body)}
.eyebrow--accent{color:var(--accent)}
.eyebrow--blue{color:var(--blue);letter-spacing:normal;text-transform:none}

.h1{font-size:var(--fs-h1);font-weight:600;line-height:1.06;letter-spacing:-.03em}
.h1--hero{font-size:var(--fs-h1-hero);line-height:1.04;letter-spacing:-.035em}
.h2{font-size:var(--fs-h2);font-weight:600;line-height:1.08;letter-spacing:-.02em}
.h2--sm{font-size:var(--fs-h2-sm);line-height:1.12}
.h3{font-size:var(--fs-h3);font-weight:600;letter-spacing:-.02em;line-height:1.25}
.h4{font-size:var(--fs-h3);font-weight:600;letter-spacing:-.02em;line-height:1.25}
.lead{font-size:var(--fs-lead);line-height:1.45;letter-spacing:-.01em;text-wrap:pretty}
.muted{color:var(--ink-muted);text-wrap:pretty}
.faint{color:var(--ink-faint)}
.on-dark-body{color:var(--dark-body);text-wrap:pretty}
.mono{font-family:var(--sans);font-size:15px}
.num{font-family:var(--fig);font-variant-numeric:tabular-nums}
.note{font-family:var(--sans);font-size:15px;color:var(--ink-faint)}

@media (min-width:901px){
  .m-20ch{max-width:20ch}.m-22ch{max-width:22ch}.m-24ch{max-width:24ch}
  .m-26ch{max-width:26ch}.m-40ch{max-width:40ch}.m-42ch{max-width:42ch}
  .m-44ch{max-width:44ch}.m-46ch{max-width:46ch}.m-52ch{max-width:52ch}
  .m-54ch{max-width:54ch}.m-56ch{max-width:56ch}.m-58ch{max-width:58ch}
  .m-60ch{max-width:60ch}.m-62ch{max-width:62ch}.m-64ch{max-width:64ch}
  .m-68ch{max-width:68ch}.m-88ch{max-width:88ch}
}

/* --- Buttons -------------------------------------------------------------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  padding:15px 26px;
  border:1px solid transparent;border-radius:var(--radius-sm);
  font-family:var(--sans);font-size:16px;font-weight:500;
  line-height:1.2;text-decoration:none;white-space:nowrap;cursor:pointer;
  transition:background-color .15s ease,border-color .15s ease,color .15s ease;
}
.btn--sm{padding:14px 24px}
.btn--xs{padding:12px 22px}
.btn--primary{background:var(--brand);color:#fff}
.btn--primary:hover{background:var(--brand-600);color:#fff}
/* On dark ground the primary flips to white so it stays the loudest thing.
   All five blue grounds are listed — a brand-blue button on a brand-blue band
   would disappear. */
.hero .btn--primary,.section--dark .btn--primary,.section--blue .btn--primary,
.section--deep .btn--primary,.closer .btn--primary{background:var(--surface-raised);color:var(--ink)}
.hero .btn--primary:hover,.section--dark .btn--primary:hover,.section--blue .btn--primary:hover,
.section--deep .btn--primary:hover,.closer .btn--primary:hover{background:#e7eae9;color:var(--ink)}
.btn--ghost{border-color:var(--hair);color:var(--ink)}
.btn--ghost:hover{border-color:var(--brand);color:var(--brand)}
.btn--ghost-dark{border-color:var(--dark-border);color:var(--dark-fg)}
.btn--ghost-dark:hover{border-color:var(--dark-fg);color:var(--dark-fg)}
.btn--ghost-light{border-color:rgba(255,255,255,.45);color:#fff}
.btn--ghost-light:hover{border-color:#fff;color:#fff}
.btn--phone{font-size:var(--fs-lead);letter-spacing:.04em;text-transform:none}

.btn-row{display:flex;flex-wrap:wrap;align-items:center;gap:16px 20px}
.btn-row--tight{gap:16px}

.link-arrow{display:inline-flex;align-items:center;gap:8px;padding-bottom:2px;
  border-bottom:1px solid var(--hair);
  font-family:var(--sans);font-size:var(--fs-mono);
  text-decoration:none;white-space:nowrap;
  transition:border-color .15s ease}
.link-arrow:hover{border-color:var(--brand);color:var(--brand)}

@media (max-width:640px){
  .btn-row{flex-direction:column;align-items:stretch;gap:12px}
  .btn-row .btn{width:100%}
  .btn-row .note,.btn-row .link-arrow{align-self:flex-start}
  .btn{padding:16px 22px}
  .btn--sm,.btn--xs{padding:14px 22px}
}

/* --- Header --------------------------------------------------------------- */
.utility{
  background:var(--dark);color:var(--dark-body);
  font-family:var(--sans);font-size:14px;
}
.utility__inner{
  max-width:var(--measure);margin:0 auto;padding:0 var(--gutter);
  display:flex;align-items:center;justify-content:space-between;gap:24px;min-height:38px;
}
.utility a{text-decoration:none}
/* The rating travels in the bar rather than occupying a section of its own.
   Svea Solar does the same with Trustpilot, and it is strictly better: proof is
   visible on every page and every scroll position instead of once, and it frees
   the start page of an 89-line carousel. */
@media (max-width:640px){
  .utility__inner{flex-wrap:wrap;gap:4px 16px;padding-top:6px;padding-bottom:6px}
}

.site-header{background:var(--surface-raised);border-bottom:1px solid var(--hair);position:relative;z-index:100}
.site-header__inner{
  max-width:var(--measure);margin:0 auto;padding:0 var(--gutter);
  display:flex;align-items:center;justify-content:space-between;gap:24px;min-height:76px;
}
.site-header__logo{flex:none;line-height:0;display:block}
.site-header__logo img{width:168px;height:auto}
.site-header__logo--light{display:none}
.site-nav ul{display:flex;align-items:center;gap:26px}
/* Top-level links only. The "Mer" dropdown lives inside .site-nav, so a bare
   `.site-nav a` also paints the panel's links — which is how they ended up
   white-on-white over the hero. Every bar-level rule is scoped to the direct
   children; .nav-menu owns its own links. */
.site-nav > ul > li > a{
  position:relative;display:block;padding:8px 0;
  font-size:14px;font-weight:500;color:var(--ink-muted);text-decoration:none;white-space:nowrap;
}
.site-nav > ul > li > a:hover{color:var(--ink)}
.site-nav > ul > li > a[aria-current="page"]{color:var(--ink)}
.site-nav > ul > li > a[aria-current="page"]::after{
  content:"";position:absolute;left:0;right:0;bottom:-2px;height:2px;background:var(--accent);
}
.nav-toggle{display:none}

/* Overlay header (hero pages) */
.site-header--overlay{
  position:absolute;top:0;left:0;right:0;z-index:3;
  background:transparent;border-bottom:0;
}
.site-header--overlay .utility{
  background:transparent;color:rgba(255,255,255,.72);
  border-bottom:1px solid rgba(255,255,255,.16);
}
.site-header--overlay .utility a:hover{color:#fff}
/* Mejladressen lyfts ur barens 72 % vitt. Den är den enda av de tre
   uppgifterna man ska kunna läsa av på en gång, och mot ett fotografi var den
   det som föll bort först. Betyget och klockslaget ligger kvar på 72: de är
   sammanhang, inte kontaktväg. */
.site-header--overlay .utility__mail{color:rgba(255,255,255,.94)}
.site-header--overlay .site-header__logo img{width:104px}
.site-header--overlay .site-header__logo--dark{display:none}
.site-header--overlay .site-header__logo--light{display:block}
.site-header--overlay .site-nav > ul > li > a{color:rgba(255,255,255,.8)}
.site-header--overlay .site-nav > ul > li > a:hover,
.site-header--overlay .site-nav > ul > li > a[aria-current="page"]{color:#fff}

.site-header__panel{display:none}

@media (max-width:1100px){
  .utility{display:none}
  .site-header__inner{min-height:64px;padding:0 var(--gutter)}
  .site-header__logo img{width:132px}
  .site-header--overlay .site-header__logo img{width:84px}
  .site-header--overlay .site-header__inner{border-bottom:1px solid rgba(255,255,255,.16)}
  .site-header__cta{display:none}
  .site-nav{display:none}
  .nav-toggle{flex:none;display:inline-flex;align-items:center;gap:8px;
    padding:10px 14px;border:1px solid var(--hair);border-radius:var(--radius-sm);
    background:none;color:var(--ink);
    font-family:var(--sans);font-size:13px;cursor:pointer}
  .site-header--overlay .nav-toggle{border-color:rgba(255,255,255,.4);color:#fff}
  .nav-toggle__bars{position:relative;display:block;width:16px;height:10px}
  .nav-toggle__bars span{position:absolute;left:0;right:0;height:1.5px;background:currentColor}
  .nav-toggle__bars span:first-child{top:0}
  .nav-toggle__bars span:last-child{bottom:0}

  .site-header__panel[data-open="true"]{
    display:block;background:var(--surface-raised);
    border-top:1px solid var(--hair);border-bottom:1px solid var(--hair);
    padding:16px var(--gutter) 32px;
  }
  /* :not(.btn) matters: without it this rule also paints the panel's
     primary button, which put --ink-muted on --brand at roughly 1.9:1. */
  .site-header__panel a:not(.btn){display:block;font-size:16px;font-weight:500;color:var(--ink-muted);text-decoration:none}
  .site-header__panel li{border-bottom:1px solid var(--hair)}
  .site-header__panel li a{padding:16px 0}
  .site-header__panel li a[aria-current="page"]{color:var(--ink)}
  .site-header__panel__meta{
    display:flex;flex-direction:column;gap:8px;margin-top:24px;
    font-family:var(--sans);font-size:15px;
  }
  .site-header__panel .btn{display:flex;margin-top:24px;width:100%}
}

/* --- Hero ----------------------------------------------------------------- */
.hero{position:relative;display:flex;align-items:stretch;min-height:740px;background-color:var(--dark);overflow:hidden}
.hero__media{position:absolute;inset:0;margin:0}
.hero__media img{width:100%;height:100%;object-fit:cover;object-position:center 58%}
/* scrim is the deepest brand blue, not neutral black — the photo sinks into
   the palette instead of into a grey hole. Alphas nudged up to hold the same
   text contrast now that the tint is a shade lighter than #12191D. */
.hero__scrim{
  position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(1,44,60,.75) 0%,rgba(1,44,60,.47) 42%,rgba(1,44,60,.84) 100%);
}
.hero__body{
  position:relative;z-index:1;display:flex;flex-direction:column;
  width:100%;max-width:var(--measure);margin:0 auto;
  padding:172px var(--gutter) 40px;
}
.hero__rating{display:flex;align-items:center;gap:12px;margin-bottom:28px}
.hero h1{margin-bottom:24px;color:#fff}
.hero__lead{margin-bottom:36px;color:rgba(255,255,255,.86)}
.hero__rating b{font-family:var(--fig);font-size:14px;font-weight:500;color:#fff}
.hero__rating span:last-child{font-size:14px;color:rgba(255,255,255,.82)}
.hero__brands{display:flex;flex-wrap:wrap;align-items:center;gap:20px 44px;margin-top:auto;padding-top:72px}
.hero__brands p{flex:none;font-family:var(--sans);font-size:var(--fs-mono);color:rgba(255,255,255,.6)}
.hero__brands ul{display:flex;flex-wrap:wrap;align-items:center;gap:20px 44px}
.hero__brands img{height:30px;width:auto;filter:brightness(0) invert(1);opacity:.8}

@media (max-width:900px){
  .hero{min-height:640px}
  .hero__body{padding:120px var(--gutter) 32px}
  .hero__rating{gap:10px;margin-bottom:20px}
  .hero h1{margin-bottom:16px}
  .hero__lead{margin-bottom:28px}
  .hero__brands{gap:16px 28px;padding-top:56px}
  .hero__brands ul{gap:16px 26px}
  .hero__brands img{height:20px}
}

/* --- Page header (non-hero pages) ---------------------------------------- */
.page-head{max-width:var(--measure);margin:0 auto;padding:var(--pad-md) var(--gutter) var(--pad-sm)}
.page-head .eyebrow{margin-bottom:16px}
.page-head h1{margin-bottom:24px}

/* --- Stars ---------------------------------------------------------------- */
.stars{display:flex;gap:3px}
.stars svg{width:15px;height:15px;fill:var(--accent)}
.stars svg.is-empty{fill:var(--hair)}
.stars--sm svg{width:14px;height:14px}

/* --- Split (label column + content column) -------------------------------- */
.split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:96px;align-items:start}
.split--narrow{grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);gap:112px}
.split--end{align-items:end}
.split--tight{gap:96px}
@media (max-width:980px){
  .split,.split--narrow,.split--tight{grid-template-columns:minmax(0,1fr);gap:32px}
}

/* --- Numbered list (metod / USP teaser) ----------------------------------- */
.rule-list li{
  display:grid;grid-template-columns:auto 1fr;gap:14px;align-items:baseline;
  padding:12px 0;border-top:1px solid var(--hair);
}
.rule-list li:last-child{border-bottom:1px solid var(--hair)}
.rule-list .num{font-size:var(--fs-mono);color:var(--blue)}

.reason-list li{display:grid;grid-template-columns:auto 1fr;gap:24px;padding:32px 0;border-top:1px solid var(--hair)}
.reason-list .num{font-size:var(--fs-mono);line-height:1.7;color:var(--ink-faint)}
.reason-list h3{margin-bottom:8px}
@media (max-width:900px){
  .reason-list li{gap:16px;padding:24px 0}
  .reason-list h3{margin-bottom:6px}
}

/* --- Column grids --------------------------------------------------------- */
.cols{display:grid;gap:32px}
.cols--4{grid-template-columns:repeat(4,minmax(0,1fr))}
.cols--3{grid-template-columns:repeat(3,minmax(0,1fr))}
.cols--2{grid-template-columns:repeat(2,minmax(0,1fr))}
.cols--gap-24{gap:24px}
.cols--gap-16{gap:16px}
@media (max-width:900px){
  .cols--4,.cols--3{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:640px){
  .cols--4,.cols--3,.cols--2{grid-template-columns:minmax(0,1fr);gap:20px}
}

.tile{padding-top:24px;border-top:1px solid var(--hair)}
.tile h3{margin-bottom:8px}
.tile--sm{padding-top:20px}
.tile--sm h3{font-size:19px}
.tile--sm p{font-size:16px}

/* --- Section heading rows ------------------------------------------------- */
.head-row{display:flex;flex-wrap:wrap;align-items:end;justify-content:space-between;gap:24px;margin-bottom:40px}
.head-row .eyebrow{margin-bottom:16px}
.head-row h2{margin-bottom:0}

/* --- Process (ordered steps) ---------------------------------------------- */
.process{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:32px}
.process li{padding-top:24px;border-top:1px solid var(--hair)}
.process__num{
  display:block;margin-bottom:16px;font-family:var(--fig);
  font-size:var(--fs-num-lg);line-height:1;color:var(--hair);font-variant-numeric:tabular-nums;
}
.process h3{margin-bottom:8px}
@media (max-width:900px){.process{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:640px){.process{grid-template-columns:minmax(0,1fr);gap:24px}}

/* --- Cards ---------------------------------------------------------------- */
.card{background:var(--surface-raised);border:1px solid var(--hair);border-radius:var(--radius);padding:var(--card-pad)}
.card--flat{background:var(--surface);border-style:dashed}
.card--dark{background-color:var(--dark);border-color:transparent;color:var(--dark-fg)}
.card--dark p{color:var(--dark-body);text-wrap:pretty}
.card--plain{padding:32px 24px}
.card__stack{display:flex;flex-direction:column;gap:12px}

/* --- Compare (lönsamhet) -------------------------------------------------- */
.compare{display:grid;grid-template-columns:1fr 1fr;gap:24px}
@media (max-width:860px){.compare{grid-template-columns:minmax(0,1fr)}}
.compare article{display:flex;flex-direction:column;gap:24px}
.compare__head{display:flex;align-items:baseline;justify-content:space-between;gap:16px}
.compare__tag{font-family:var(--sans);font-size:var(--fs-mono);white-space:nowrap}
.compare__label{margin-bottom:10px;font-family:var(--sans);font-size:var(--fs-mono);color:var(--ink-faint)}
.compare__figure{font-family:var(--fig);font-size:var(--fs-num-lg);font-weight:500;line-height:1;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.meter{height:10px;background:var(--surface-sunken);border-radius:999px;overflow:hidden}
.meter span{display:block;height:100%;border-radius:999px}
.meter+p{margin-top:10px;font-family:var(--fig);font-size:var(--fs-mono-sm);letter-spacing:.04em;color:var(--ink-faint)}

/* --- Swing (startsidans avkastning) --------------------------------------- */
/* Two bars and the difference between them. The bars are the argument: 5 397
   next to 15 750 needs no sentence explaining it. The figure below is the same
   subtraction the reader can check in their head — 15 750 − 5 397 = 10 353 —
   which is the whole reason it is worth showing all three rather than just the
   saving. Reuses .meter, so the bar animation and track colour are shared with
   the breakdown on lonsamhet.html.
   A third row (what the anläggning costs) slots in without touching the CSS,
   which is where the price bands go once they exist. */
.swing{display:flex;flex-direction:column;gap:24px}
.swing__row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px 16px;align-items:baseline}
.swing__row .meter{grid-column:1/-1}
.swing__lbl{font-family:var(--sans);font-size:15px;color:var(--ink-muted)}
.swing__fig{font-family:var(--fig);font-size:26px;font-weight:500;line-height:1;
  letter-spacing:-.02em;font-variant-numeric:tabular-nums;color:var(--ink-muted)}
.swing__fig--good{color:var(--brand)}
.swing__sum{display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  padding-top:24px;border-top:1px solid var(--hair)}
.swing__sum-lbl{font-family:var(--sans);font-size:15px;font-weight:500;color:var(--ink)}
.swing__sum-fig{font-family:var(--fig);font-size:var(--fs-num-lg);font-weight:500;
  line-height:1;letter-spacing:-.03em;color:var(--brand);font-variant-numeric:tabular-nums}
.swing__note{font-size:13px;line-height:1.55;color:var(--ink-faint);text-wrap:pretty}
@media (max-width:600px){
  .swing__row{grid-template-columns:minmax(0,1fr)}
  .swing__fig{font-size:22px}
}

.kv{margin:0;border-top:1px solid var(--hair)}
.kv>div{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:12px 0;border-bottom:1px solid var(--hair)}
.kv dt{font-size:16px;color:var(--ink-muted)}
.kv dd{margin:0;font-family:var(--fig);font-size:16px;font-variant-numeric:tabular-nums;color:var(--ink)}

.savings{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:16px 40px;
  padding:32px 0;border-top:1px solid var(--hair);border-bottom:1px solid var(--hair);margin-bottom:40px;
}
.savings__figure{
  font-family:var(--fig);font-size:var(--fs-num-xl);font-weight:500;line-height:1;
  letter-spacing:-.03em;color:var(--blue);font-variant-numeric:tabular-nums;
}
.footnote-row{
  display:flex;flex-wrap:wrap;align-items:end;justify-content:space-between;gap:32px;
  margin-top:40px;padding-top:32px;border-top:1px solid var(--hair);
}

/* --- Compact review banner ----------------------------------------------
   Replaces the carousel on the start page. Two real quotes, verbatim and
   unedited — the shortest two of the eleven, chosen so the band stays one row
   rather than trimming somebody's words to fit. The aggregate and the
   verification note stay, because the Omnibus rules require saying how reviews
   are handled and because publishing the unflattering half is the point. */
.reviewbar{padding:var(--pad-sm) 0;border-top:1px solid var(--hair)}
.reviewbar__inner{display:grid;grid-template-columns:auto minmax(0,1fr);gap:48px;align-items:start}
.reviewbar__score{display:flex;flex-direction:column;gap:8px;flex:none}
.reviewbar__figure{display:flex;align-items:baseline;gap:10px}
.reviewbar__figure b{font-family:var(--fig);font-size:38px;font-weight:500;
  line-height:1;letter-spacing:-.03em;color:var(--brand)}
.reviewbar__figure span{font-size:15px;color:var(--ink-muted)}
.reviewbar__note{max-width:34ch;font-size:13px;line-height:1.55;color:var(--ink-faint);text-wrap:pretty}
.reviewbar__quotes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:32px;margin:0;padding:0;list-style:none}
.reviewbar__quotes li{display:flex;flex-direction:column;gap:10px}
.reviewbar__quotes p{font-size:17px;line-height:1.5;color:var(--ink);text-wrap:pretty}
.reviewbar__quotes cite{font-family:var(--sans);font-size:14px;font-weight:500;
  font-style:normal;color:var(--ink-muted)}
@media (max-width:860px){
  .reviewbar__inner{grid-template-columns:minmax(0,1fr);gap:28px}
  .reviewbar__quotes{grid-template-columns:minmax(0,1fr);gap:24px}
}

/* --- Reviews carousel ----------------------------------------------------- */
.reviews{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:56px;align-items:start;padding-top:32px;border-top:1px solid var(--brand)}
.reviews__viewport{overflow:hidden}
.reviews__track{display:flex;transition:transform .45s cubic-bezier(.4,0,.2,1)}
.reviews__track article{flex:0 0 100%;display:flex;flex-direction:column;gap:24px;padding-right:56px}
.reviews__quote{max-width:42ch;font-size:var(--fs-h2-sm);line-height:1.3;letter-spacing:-.02em;text-wrap:pretty}
.reviews__by{font-family:var(--sans);font-size:15px;font-weight:500;color:var(--ink-muted)}
.reviews__aside{display:flex;flex-direction:column;align-items:flex-end;gap:20px;width:140px}
.reviews__count{font-family:var(--fig);font-size:var(--fs-mono);letter-spacing:.06em;color:var(--ink-faint);font-variant-numeric:tabular-nums}
.reviews__bar{width:140px;height:2px;background:var(--hair)}
.reviews__bar span{display:block;height:100%;background:var(--brand);transition:width .45s ease}
.reviews__nav{display:flex;gap:8px}
.reviews__nav button{
  display:inline-flex;align-items:center;justify-content:center;
  width:48px;height:48px;flex:none;
  border:1px solid var(--hair);border-radius:var(--radius-sm);
  background:var(--surface-raised);color:var(--ink);font-size:18px;line-height:1;cursor:pointer;
  transition:border-color .15s ease;
}
.reviews__nav button:hover{border-color:var(--brand);color:var(--brand)}
@media (max-width:900px){
  .reviews{grid-template-columns:minmax(0,1fr);gap:24px;padding-top:20px}
  .reviews__track article{gap:14px;padding-right:0}
  .reviews__aside{flex-direction:row;align-items:center;justify-content:space-between;width:100%}
  .reviews__bar{flex:1;margin:0 16px}
}

/* --- Figure / chart card -------------------------------------------------- */
.figure-card{margin-top:var(--pad-sm);padding:var(--card-pad);background:var(--surface-raised);border:1px solid var(--hair);border-radius:var(--radius)}
.figure-card figcaption{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:16px;margin-bottom:24px}
.figure-card__note{max-width:88ch;margin-top:24px;padding-top:24px;border-top:1px solid var(--hair);font-size:16px;line-height:1.6;color:var(--ink-muted);text-wrap:pretty}

/* --- Checklist (vad du får, dark) ----------------------------------------- */
.checklist{display:grid;grid-template-columns:1fr 1fr;gap:0 48px}
.checklist li{display:grid;grid-template-columns:auto minmax(0,1fr);gap:16px;align-items:start;padding:20px 0;border-top:1px solid var(--dark-hair)}
.checklist svg{width:18px;height:18px;flex:none;margin-top:3px}
.checklist h3{margin-bottom:4px;font-size:19px;letter-spacing:-.01em}
.checklist p{font-size:16px;line-height:1.6;color:var(--dark-body);text-wrap:pretty}
@media (max-width:860px){.checklist{grid-template-columns:minmax(0,1fr)}}

/* --- Accordion (USP) ------------------------------------------------------ */
.accordion{border-top:1px solid var(--hair)}
.accordion li{border-bottom:1px solid var(--hair)}
.accordion__trigger{
  display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:20px;
  width:100%;padding:24px 0;border:0;background:none;text-align:left;cursor:pointer;
  font-family:var(--sans);color:var(--ink);transition:color .15s ease;
}
.accordion__trigger:hover{color:var(--blue)}
.accordion__title{font-size:var(--fs-h3);font-weight:600;letter-spacing:-.02em}
.accordion__sign{
  font-family:var(--fig);font-size:24px;line-height:1;color:var(--ink-faint);
  transition:transform .2s ease;
}
.accordion__trigger[aria-expanded="true"] .accordion__sign{transform:rotate(45deg);color:var(--ink)}
.accordion__panel{display:none}
.accordion__panel[data-open="true"]{display:block}
.accordion__panel p{max-width:58ch;padding:0 0 28px 42px;font-size:18px;line-height:1.6;color:var(--ink-muted);text-wrap:pretty}
@media (max-width:640px){
  .accordion__trigger{gap:12px;padding:20px 0}
  .accordion__panel p{padding:0 0 24px 0;font-size:16px}
}

/* --- Tabs ----------------------------------------------------------------- */
.tabs{display:flex;gap:32px;border-bottom:1px solid var(--hair);overflow-x:auto;scrollbar-width:none}
.tabs::-webkit-scrollbar{display:none}
.tabs--sub{gap:24px}
.tab{
  position:relative;padding:0 0 16px;border:0;background:none;cursor:pointer;
  font-family:var(--sans);font-size:var(--fs-h3);font-weight:600;letter-spacing:-.02em;
  color:var(--ink-faint);white-space:nowrap;transition:color .15s ease;
}
.tab:hover{color:var(--ink-muted)}
.tab[aria-selected="true"]{color:var(--ink)}
.tab[aria-selected="true"]::after{
  content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--accent);
}
.tabs--sub .tab{font-size:16px;font-family:var(--sans);font-weight:500}
.tabpanel{display:none}
.tabpanel[data-open="true"]{display:block}
.tabpanels{padding-top:48px}
.tabpanels--sub{padding-top:32px}
@media (max-width:900px){
  .tabs{gap:20px}
  .tabpanels{padding-top:32px}
}

/* --- Product feature card (image + copy) ---------------------------------- */
.feature{display:grid;grid-template-columns:1.05fr 1fr;background:var(--surface-raised);border:1px solid var(--hair);border-radius:var(--radius);overflow:hidden}
.feature__media{min-height:420px;background:var(--surface);border-right:1px solid var(--hair)}
.feature__media img{width:100%;height:100%;object-fit:cover}
.feature__body{display:flex;flex-direction:column;gap:20px;padding:40px}
.feature__list{display:flex;flex-direction:column;gap:12px;padding-top:20px;border-top:1px solid var(--hair)}
.feature__list ul{display:flex;flex-direction:column;gap:8px;color:var(--ink-muted)}
@media (max-width:860px){
  .feature{grid-template-columns:minmax(0,1fr)}
  .feature__media{min-height:0;aspect-ratio:4/3;border-right:0;border-bottom:1px solid var(--hair)}
  .feature__body{padding:24px 20px;gap:16px}
}

.gallery figure{margin:0;aspect-ratio:4/3;background:var(--surface);border:1px solid var(--hair);border-radius:var(--radius);overflow:hidden}
.gallery img{width:100%;height:100%;object-fit:cover}

.supplier{display:flex;flex-wrap:wrap;align-items:center;gap:24px 32px;margin-top:48px;padding-top:24px;border-top:1px solid var(--hair)}
.supplier img{max-height:28px;width:auto;filter:grayscale(1);opacity:.75}
/* Mafi ships a white wordmark. It used to be inverted to sit on the light row,
   which painted it literal #000 — the blackest shape on the site. Masked
   instead, so the glyph is filled with the brand blue at the same weight as the
   greyscaled logos beside it. */
.supplier__mark{
  display:block;height:28px;background-color:var(--brand);opacity:.75;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
}
.supplier__mark--mafi{
  width:82px;
  -webkit-mask-image:url("../assets/img/logo-mafi.png");
  mask-image:url("../assets/img/logo-mafi.png");
}
/* JA Solar, AIKO och LONGi levererades också som vita ordmärken, samma fall
   som Mafi ovan, så de maskas på samma vis i stället för att inverteras.
   Bredden är filens egen proportion vid 28 px höjd: 385/68, 259/68, 181/68. */
/* JA Solars beskärning är bara bokstäver, utan märke eller punkt som sticker
   upp, så versalhöjden är hela filhöjden. På radens gemensamma 28 px blev
   ordet synligt större än AIKO och LONGi bredvid, som bär ett märke ovanför
   texten. Höjden är satt en bit lägre så att BOKSTÄVERNA väger lika, samma
   princip som --bh i hero-raden. 385/68 vid 24 px höjd ger 136 px. */
/* Nya filen från Julius, 8 augusti 2026: ordmärket på EN rad i stället för två,
   vitt på genomskinlig botten, beskuret till bläckets egen låda (1298x175,
   7,417:1). Höjden är därför satt som JA Solars och av samma skäl: beskärningen
   är bara bokstäver, utan märke eller punkt som sticker upp, så filhöjden ÄR
   versalhöjden och 24 px väger lika mot grannarna. Det gamla värdet, 40 px,
   räknades för den tvåradiga logotypen. Bredden är filens proportion:
   1298/175 vid 24 px ger 178. */
.supplier__mark--greenhero{
  height:24px;width:178px;
  -webkit-mask-image:url("../assets/img/logo-greenhero-1298.webp");
  mask-image:url("../assets/img/logo-greenhero-1298.webp");
}
.supplier__mark--jasolar{
  height:24px;width:136px;
  -webkit-mask-image:url("../assets/img/logo-jasolar.webp");
  mask-image:url("../assets/img/logo-jasolar.webp");
}
.supplier__mark--aiko{
  width:107px;
  -webkit-mask-image:url("../assets/img/logo-aiko.webp");
  mask-image:url("../assets/img/logo-aiko.webp");
}
/* Växelriktar- och batterimärkena, tillagda 8 augusti 2026 på kommentar 44:
   leverantörsraden fanns bara på solpanelsfliken med fem märken, medan
   växelriktare och batterier hade ETT märke var som <img>. Raden ska säga
   samma sak på alla fyra flikarna.

   Höjderna är inte satta på känsla. Karusellen i hero bär redan en tuning per
   logotyp via --bh, gjord så att varje märkes TEXT läses lika stort, och den
   relationen gäller lika mycket i den här raden. Ankaret är JA Solar, som
   finns i båda: 17 px i karusellen mot 24 här, alltså faktor 1,41. Varje höjd
   nedan är karusellens --bh gånger den faktorn. Bredden är filens egen
   proportion vid den höjden; alla sex filerna är 68 px höga. */
.supplier__mark--sigenergy{
  height:24px;width:199px;
  -webkit-mask-image:url("../assets/img/logo-sigenergy-mark.webp");
  mask-image:url("../assets/img/logo-sigenergy-mark.webp");
}
.supplier__mark--growatt{
  height:24px;width:143px;
  -webkit-mask-image:url("../assets/img/logo-growatt-mark.webp");
  mask-image:url("../assets/img/logo-growatt-mark.webp");
}
.supplier__mark--solis{
  height:31px;width:85px;
  -webkit-mask-image:url("../assets/img/logo-solis-mark.webp");
  mask-image:url("../assets/img/logo-solis-mark.webp");
}
.supplier__mark--huawei{
  height:25px;width:111px;
  -webkit-mask-image:url("../assets/img/logo-huawei-mark.webp");
  mask-image:url("../assets/img/logo-huawei-mark.webp");
}
.supplier__mark--sungrow{
  height:21px;width:171px;
  -webkit-mask-image:url("../assets/img/logo-sungrow-mark.webp");
  mask-image:url("../assets/img/logo-sungrow-mark.webp");
}
.supplier__mark--byd{
  height:37px;width:61px;
  -webkit-mask-image:url("../assets/img/logo-byd-mark.webp");
  mask-image:url("../assets/img/logo-byd-mark.webp");
}
/* Nordmount är också ett vitt märke, 456/68 vid 28 px höjd ger 188 px. */
.supplier__mark--nordmount{
  width:188px;
  -webkit-mask-image:url("../assets/img/logo-nordmount-mark.webp");
  mask-image:url("../assets/img/logo-nordmount-mark.webp");
}
.supplier__mark--longi{
  width:75px;
  -webkit-mask-image:url("../assets/img/logo-longi.webp");
  mask-image:url("../assets/img/logo-longi.webp");
}
/* Trina levererar ett vitt ordmärke med en RÖD PUNKT över i:et, på genomskinlig
   botten. Punkten behöver inte avmättas: masken använder bara alfakanalen, så
   både bokstäverna och punkten målas i samma --ink-muted som grannarna. Det är
   hela skälet till att raden maskar vita märken i stället för att invertera dem
   — se noten vid .supplier__mark.
   1036/243 vid 28 px höjd ger 119 px. */
.supplier__mark--trina{
  width:119px;
  -webkit-mask-image:url("../assets/img/logo-trina.webp");
  mask-image:url("../assets/img/logo-trina.webp");
}

/* --- Frequency panel ------------------------------------------------------ */
.freq{background:var(--surface-raised);border:1px solid var(--hair);border-radius:var(--radius);padding:var(--card-pad)}
.freq__head{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:32px}
.freq__head h3{font-family:var(--sans);font-size:var(--fs-mono);font-weight:400;color:var(--ink-muted)}
.freq__badge{display:inline-flex;align-items:center;gap:8px;padding:4px 10px;
  border:1px solid var(--hair);border-radius:999px;
  font-family:var(--sans);font-size:var(--fs-mono);
  color:var(--ink-muted);white-space:nowrap}
.freq__badge span:first-child{width:7px;height:7px;border-radius:50%;background:var(--ink-faint);flex:none}
.freq__value{display:flex;align-items:baseline;gap:8px;margin-bottom:32px}
.freq__value b{font-family:var(--fig);font-size:var(--fs-num-xl);font-weight:500;line-height:1;letter-spacing:-.03em;color:var(--blue);font-variant-numeric:tabular-nums}
.freq__value span{font-family:var(--fig);font-size:var(--fs-lead);color:var(--ink-muted)}
.freq__scale{position:relative;height:1px;background:var(--hair);margin-bottom:16px}
.freq__scale span:first-child{position:absolute;left:50%;top:-5px;width:1px;height:11px;background:var(--ink-faint)}
.freq__scale span:last-child{position:absolute;left:56%;top:-4px;width:2px;height:9px;background:var(--blue);transform:translateX(-1px)}
.freq__ticks{display:flex;justify-content:space-between;font-family:var(--fig);font-size:var(--fs-mono);color:var(--ink-muted);font-variant-numeric:tabular-nums}
.freq__foot{margin-top:24px;padding-top:24px;border-top:1px solid var(--hair);font-size:14px;line-height:1.55;color:var(--ink-muted)}
.freq__foot span{display:block;margin-top:8px;font-family:var(--fig);font-size:var(--fs-mono);color:var(--ink-faint)}

/* --- Video facade --------------------------------------------------------- */
.video-card{display:flex;flex-direction:column;background:var(--surface-raised);border:1px solid var(--hair);border-radius:var(--radius);overflow:hidden}
.video-card__frame{position:relative;aspect-ratio:16/9;background:var(--dark);border-bottom:1px solid var(--hair);overflow:hidden}
.video-card__play{position:absolute;inset:0;width:100%;height:100%;padding:0;border:0;background:none;cursor:pointer}
.video-card__play img{width:100%;height:100%;object-fit:cover;transform:scale(1.35);opacity:.9;transition:opacity .2s ease}
.video-card__play:hover img{opacity:1}
.video-card__play span{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none}
.video-card__play svg{width:54px;height:54px}
.video-card__body{display:flex;flex-direction:column;gap:12px;padding:24px}
.video-card iframe{position:absolute;inset:0;width:100%;height:100%;border:0;z-index:2}
@media (max-width:640px){.video-card__body{padding:20px}}

/* --- Case cards ----------------------------------------------------------- */
.filters{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:32px}
.filters>span{font-family:var(--sans);font-size:var(--fs-mono);color:var(--ink-faint);margin-right:4px}
.filter{
  padding:8px 16px;border:1px solid var(--hair);border-radius:999px;background:var(--surface-raised);
  font-family:var(--fig);font-size:var(--fs-mono);letter-spacing:.04em;color:var(--ink-muted);
  cursor:pointer;white-space:nowrap;transition:border-color .15s ease,background-color .15s ease,color .15s ease;
}
.filter:hover{border-color:var(--brand-300);color:var(--brand)}
.filter[aria-pressed="true"]{background:var(--brand);border-color:var(--brand);color:#fff}

.case-list{display:flex;flex-direction:column;gap:32px}
.case{background:var(--surface-raised);border:1px solid var(--hair);border-radius:var(--radius);overflow:hidden}
.case[hidden]{display:none}
.case__top{display:grid;grid-template-columns:1fr 1fr}
.case__media{aspect-ratio:4/3;background:var(--surface);border-right:1px solid var(--hair)}
.case__media img{width:100%;height:100%;object-fit:cover}
.case__intro{display:flex;flex-direction:column;justify-content:center;gap:14px;padding:32px}
.case__meta{font-family:var(--sans);font-size:var(--fs-mono);color:var(--ink-muted)}
.case__intro h3{font-size:var(--fs-h2-sm)}
.case__specs{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:32px;padding:32px;border-top:1px solid var(--hair);background:var(--surface)}
.spec{padding-top:20px;border-top:1px solid var(--hair)}
.spec h4{margin-bottom:8px;font-family:var(--sans);font-size:var(--fs-mono);font-weight:400;color:var(--ink-faint)}
.spec__row{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:7px 0}
.spec__row span:first-child{font-size:14px;color:var(--ink-muted)}
.spec__row span:last-child{font-family:var(--fig);font-size:14px;font-variant-numeric:tabular-nums;color:var(--ink)}
.spec__row--total{margin-top:4px;border-top:1px solid var(--hair)}
.spec__row--total span:first-child{color:var(--ink);font-weight:600}
.spec__row--total span:last-child{font-weight:500}
@media (max-width:1080px){.case__specs{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:860px){
  .case__top{grid-template-columns:minmax(0,1fr)}
  .case__media{aspect-ratio:16/9;border-right:0;border-bottom:1px solid var(--hair)}
  .case__intro{padding:20px;gap:10px}
  .case__specs{grid-template-columns:minmax(0,1fr);gap:20px;padding:20px}
  .spec{padding-top:16px}
  .spec__row{padding:6px 0}
  .spec__row span{font-size:13px}
}

/* --- Footer --------------------------------------------------------------- */
.site-footer{background:var(--surface-raised);border-top:1px solid var(--hair);padding:var(--pad-md) 0 32px}
.site-footer__grid{display:grid;grid-template-columns:1.4fr 1fr 1fr;gap:32px}
.site-footer__logo{display:block;line-height:0}
.site-footer__logo img{width:168px;height:auto;margin-bottom:16px}
.site-footer p.site-footer__blurb{margin:0;font-family:var(--sans);font-size:15px;line-height:1.6;color:var(--ink-muted)}
.site-footer h2{margin:0 0 16px;font-family:var(--sans);font-size:var(--fs-mono);font-weight:400;color:var(--ink-muted)}
.site-footer ul{display:flex;flex-direction:column;gap:8px}
.site-footer ul a{font-size:14px;color:var(--ink-muted);text-decoration:none}
.site-footer ul a:hover{color:var(--ink)}
.site-footer__legal{
  display:flex;flex-wrap:wrap;gap:8px 24px;margin:var(--pad-md) 0 0;padding-top:24px;
  border-top:1px solid var(--hair);font-family:var(--fig);font-size:var(--fs-mono);color:var(--ink-muted);
}
@media (max-width:860px){
  .site-footer__grid{grid-template-columns:minmax(0,1fr);gap:40px}
  .site-footer__logo img{width:148px}
  .site-footer nav ul{display:grid;grid-template-columns:1fr 1fr;gap:10px 16px}
  .site-footer__legal{margin-top:48px}
}

/* --- Misc ----------------------------------------------------------------- */
.visually-hidden{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
.skip-link{
  position:absolute;left:-9999px;top:0;z-index:100;
  padding:12px 20px;background:var(--accent);color:var(--ink);
  font-family:var(--fig);font-size:14px;text-decoration:none;
}
.skip-link:focus{left:0}

/* --- Contact cards -------------------------------------------------------- */
.contact-card{padding:var(--card-pad);background:var(--surface-raised);border:1px solid var(--hair);border-radius:var(--radius);display:flex;flex-direction:column;gap:12px}
.contact-card h3{font-family:var(--sans);font-size:var(--fs-mono);font-weight:400;color:var(--ink-muted)}
.contact-card a,.contact-card p{font-size:var(--fs-h3);font-weight:600;letter-spacing:-.02em;text-decoration:none;line-height:1.25}
.contact-card small{font-family:var(--fig);font-size:var(--fs-mono);color:var(--ink-faint);font-weight:400;letter-spacing:.02em}

/* --- Map embed ------------------------------------------------------------ */
.map-embed{border:1px solid var(--hair);border-radius:var(--radius);overflow:hidden;background:var(--surface-raised)}
.map-embed iframe{width:100%;height:680px;border:0;display:block}
@media (max-width:760px){.map-embed iframe{height:940px}}

/* --- County grid ---------------------------------------------------------- */
.lan{padding-top:24px;border-top:1px solid var(--hair)}
.lan h3{margin-bottom:4px}
.lan__count{margin-bottom:16px;font-family:var(--sans);font-size:var(--fs-mono);color:var(--ink-faint)}
.lan ul{display:grid;grid-template-columns:1fr 1fr;gap:0 20px}
.lan li{padding:7px 0;border-bottom:1px solid var(--hair);font-size:16px;color:var(--ink-muted)}
@media (max-width:640px){.lan ul{grid-template-columns:1fr}}

/* --- House size cards ----------------------------------------------------- */
.houses{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px}
.house{display:flex;flex-direction:column;background:var(--surface-raised);border:1px solid var(--hair);border-radius:var(--radius);overflow:hidden}
.house__media{aspect-ratio:4/3;background:var(--surface);border-bottom:1px solid var(--hair);display:grid;place-items:center}
.house__media img{width:100%;height:100%;object-fit:contain}
.house__body{display:flex;flex-direction:column;gap:8px;padding:24px}

/* ==========================================================================
   Galleri — a justified photo wall that configures itself
   The number of photos per slide varies and is not known when this is written,
   so nothing here counts children or targets a slide. Each figure declares its
   own aspect ratio in --ar and the row maths falls out of that:

     flex-basis ∝ --ar   → within a row, widths land in proportion to aspect
     aspect-ratio: --ar  → so every figure in that row resolves to one height

   Rows therefore justify edge to edge whatever mix of portrait and landscape
   arrives. js/site.js fills in --ar from the file's own dimensions, so adding
   a photo is adding an <img> and nothing else.
   ========================================================================== */
.gal{display:flex;flex-wrap:wrap;gap:14px;margin:0}
.gal__item{
  --ar:1.5;                 /* replaced per photo — this is only the fallback */
  flex:var(--ar) 1 calc(var(--ar) * 210px);
  /* A short last row would otherwise stretch one photo across the full width.
     Capping the width caps the row height instead, so a leftover sits at a
     sane size and simply leaves space to its right. 320 is tuned so a lone
     leftover lands at roughly the height of a full row rather than looming
     20% larger than the photos above it. */
  max-width:calc(var(--ar) * 320px);
  aspect-ratio:var(--ar);
  min-width:min(160px,100%);
  margin:0;position:relative;overflow:hidden;
  border:1px solid var(--hair);border-radius:var(--radius);
  background-color:var(--surface);
  cursor:zoom-in;
  transition:transform .3s var(--ease-out),box-shadow .3s var(--ease-out);
}
.gal__item:hover{transform:translateY(-2px);box-shadow:var(--shadow-md)}
.gal__item img{display:block;width:100%;height:100%;object-fit:cover}

/* ==========================================================================
   Photo rasterisation — why these two lines exist
   The solar-panel grid shimmered at rest but looked clean for the instant a
   card was mid-hover-transform. That is the tell: mid-transform the photo sits
   on its own compositor layer and is filtered smoothly (mip/trilinear); the
   moment the transition settles the layer is dropped and the image returns to
   the main raster path, which filters a fractional downscale poorly. A regular
   grid of panel cells is the worst possible input for that — the sampling beats
   against the cell pitch and you get moire.

   Promoting the image to its own layer keeps it on the smooth path permanently.
   Only photographs are listed: doing this to text containers softens type, and
   doing it to every image would waste layer memory for no gain. Layers only
   cost while a slide is open — hidden panels are display:none.
   ========================================================================== */
.gal__item img,
.install__media img,
.artcard .shot img,
.shot > img,
.hero__media img{
  transform:translateZ(0);
  backface-visibility:hidden;
}
.gal__item figcaption{
  position:absolute;left:0;right:0;bottom:0;padding:22px 14px 10px;
  background-image:linear-gradient(transparent,rgba(6,32,42,.72));
  color:#fff;font-family:var(--fig);font-size:var(--fs-mono-sm);
  letter-spacing:.03em;opacity:0;transition:opacity .25s ease;
}
.gal__item:hover figcaption,.gal__item:focus-visible figcaption{opacity:1}

/* Dots instead of named tabs. The houses have no names, and inventing labels
   for a slideshow is how a gallery ends up lying about which house is which —
   so position is the only thing the control claims. Still a real tablist, so
   initTabs() drives it and arrow keys work; the aria-label carries the "3 av
   11" that a sighted user reads off the dots themselves. */
.slidenav{display:flex;align-items:center;justify-content:center;gap:18px;margin-top:36px}
.slidenav__arrow{
  display:grid;place-items:center;flex:none;width:42px;height:42px;
  border:1px solid var(--hair);border-radius:50%;background-color:var(--surface-raised);
  color:var(--brand);font:17px/1 system-ui;cursor:pointer;
  transition:border-color .2s ease,color .2s ease,background-color .2s ease;
}
.slidenav__arrow:hover:not(:disabled){border-color:var(--brand);background-color:var(--brand-25)}
.slidenav__arrow:disabled{opacity:.32;cursor:default}
.slidenav__arrow:focus-visible{outline:2px solid var(--blue);outline-offset:3px}
.dots{display:flex;flex-wrap:wrap;justify-content:center;gap:11px}
.dot{
  width:10px;height:10px;padding:0;border:0;border-radius:50%;
  background-color:var(--hair);cursor:pointer;
  transition:background-color .2s ease,transform .2s var(--ease-out);
}
.dot:hover{background-color:var(--ink-faint)}
.dot[aria-selected="true"]{background-color:var(--brand);transform:scale(1.4)}
.dot:focus-visible{outline:2px solid var(--blue);outline-offset:4px}
@media (prefers-reduced-motion:reduce){.dot{transition:none}}

/* The panels sit straight under the page head now that no tab row precedes
   them, so they do not need the tab row's clearance. */
.tabpanels--flush{padding-top:0}

/* Slides differ in height by a whole row of photos, so swapping one for another
   used to snap everything below it in a single frame. site.js transitions the
   wrapper's height across the swap; this clips the taller slide while it is
   mid-collapse. The height is inline and temporary — at rest the wrapper is
   auto, so a late-loading photo or a window resize is never fighting a fixed
   pixel value. */
.tabpanels--flush.is-resizing{overflow:hidden}

/* The incoming slide's own fade would stack on top of the height transition and
   read as two separate movements. Under a height animation it only needs to
   appear. */
.tabpanels--flush.is-resizing .tabpanel[data-open="true"]{animation:none}

/* aspect-ratio carries the row maths (Chrome 88, Safari 15, Firefox 89 — early
   2021). Anything older loses the figure's height completely and the wall
   collapses, so fall back to fixed-height rows: not justified, but tidy. */
@supports not (aspect-ratio: 1 / 1) {
  .gal__item{height:230px;flex:1 1 300px;max-width:none}
  .gal__item img{height:100%}
}

.gal-empty{
  width:100%;padding:44px 20px;text-align:center;color:var(--ink-faint);
  border:1px dashed var(--hair);border-radius:var(--radius);text-wrap:pretty;
}

/* --- lightbox ------------------------------------------------------------- */
.lightbox{
  position:fixed;inset:0;z-index:200;display:none;
  place-items:center;padding:clamp(12px,3vw,40px);
  background-color:rgba(6,26,34,.92);
}
.lightbox[data-open="true"]{display:grid}
.lightbox img{max-width:100%;max-height:100%;width:auto;height:auto;
  border-radius:var(--radius);display:block}
.lightbox__bar{
  position:absolute;left:0;right:0;bottom:0;display:flex;flex-wrap:wrap;
  align-items:center;justify-content:center;gap:10px 18px;
  padding:18px 20px;color:rgba(255,255,255,.82);
  font-family:var(--fig);font-size:var(--fs-mono-sm);letter-spacing:.04em;
}
.lightbox button{
  border:1px solid rgba(255,255,255,.28);background:transparent;color:#fff;
  border-radius:999px;width:42px;height:42px;font:18px/1 system-ui;cursor:pointer;
  transition:background-color .2s ease,border-color .2s ease;
}
.lightbox button:hover{background:rgba(255,255,255,.14);border-color:#fff}
.lightbox__close{position:absolute;top:16px;right:16px}
@media (prefers-reduced-motion:reduce){
  .gal__item{transition:none}
  .gal__item:hover{transform:none}
}

/* ==========================================================================
   Artiklar — search + bento browse
   The bento is deliberately uneven: one feature card two rows tall beside two
   half-height ones, so a three-item list does not read as a stalled 3-col grid
   waiting for a fourth. Cards are <a>, so the whole tile is the hit area.
   Search is progressive — with no JS the field is hidden and every card shows.
   ========================================================================== */
.searchbar{display:grid;gap:10px;max-width:52ch}
.searchbar__field{position:relative;display:flex;align-items:center}
.searchbar__field svg{position:absolute;left:15px;width:17px;height:17px;
  color:var(--ink-faint);pointer-events:none}
.searchbar input{
  width:100%;padding:13px 15px 13px 42px;
  border:1px solid var(--hair);border-radius:999px;
  background-color:var(--surface-raised);
  font-family:var(--sans);font-size:16px;color:var(--ink);
  transition:border-color .2s ease,box-shadow .2s ease;
}
.searchbar input:focus{outline:none;border-color:var(--blue);
  box-shadow:0 0 0 3px rgba(3,125,170,.16)}
.searchbar__count{font-family:var(--fig);font-size:var(--fs-mono-sm);
  letter-spacing:.04em;color:var(--ink-faint)}
html:not(.js) .searchbar{display:none}

.bento{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  grid-auto-rows:minmax(0,auto);gap:24px}
.bento__item{display:flex}
.bento__item--feature{grid-column:span 2;grid-row:span 2}
@media (max-width:900px){
  .bento{grid-template-columns:repeat(2,minmax(0,1fr))}
  .bento__item--feature{grid-column:span 2;grid-row:auto}
}
@media (max-width:600px){
  .bento{grid-template-columns:minmax(0,1fr)}
  .bento__item--feature{grid-column:span 1}
}
.bento__item[hidden]{display:none}

.artcard{
  display:flex;flex-direction:column;width:100%;
  background-color:var(--surface-raised);border:1px solid var(--hair);
  border-radius:var(--radius);overflow:hidden;text-decoration:none;color:inherit;
  transition:border-color .25s ease,box-shadow .25s ease,transform .25s var(--ease-out);
}
.artcard:hover{border-color:var(--ink-faint);transform:translateY(-2px);
  box-shadow:var(--shadow-md)}
.artcard .shot{border-bottom:1px solid var(--hair);border-radius:0;box-shadow:none}
.artcard__body{display:flex;flex-direction:column;gap:10px;padding:24px;flex:1}
.artcard__meta{display:flex;flex-wrap:wrap;gap:6px 14px;align-items:baseline;
  font-family:var(--fig);font-size:var(--fs-mono-sm);letter-spacing:.04em;
  color:var(--ink-faint)}
.artcard__title{font-size:21px;line-height:1.25;letter-spacing:-.015em;
  text-wrap:pretty}
.bento__item--feature .artcard__title{font-size:var(--fs-h2-sm)}
.artcard__excerpt{font-size:16px;line-height:1.6;color:var(--ink-muted);
  text-wrap:pretty}
/* The byline is what stops the grid reading as anonymous content marketing —
   these are written by a person at the company, so the person is on the card. */
.artcard__foot{margin-top:auto;padding-top:14px;display:flex;flex-wrap:wrap;
  align-items:center;justify-content:space-between;gap:8px 14px}
.artcard__by{display:flex;align-items:center;gap:8px;
  font-size:14px;font-weight:500;color:var(--ink-muted)}
/* Monogram underneath, photo laid over it — the same pattern as the team grid,
   so a byline without a headshot still looks deliberate rather than broken. */
.artcard__avatar,.byline__avatar{
  position:relative;overflow:hidden;
  display:grid;place-items:center;flex:none;border-radius:50%;
  background-color:var(--brand-50);color:var(--brand);
  font-family:var(--fig);font-weight:500;letter-spacing:.04em;user-select:none;
}
.artcard__avatar img,.byline__avatar img{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center top;
}
.artcard__avatar{width:26px;height:26px;font-size:10px}
.byline__avatar{width:34px;height:34px;font-size:11px;
  box-shadow:inset 0 0 0 1px var(--brand-100)}
.artcard__more{font-family:var(--sans);font-size:15px;font-weight:500;color:var(--brand)}
.artcard:hover .artcard__more{text-decoration:underline}

.search-empty{padding:40px 0;border-top:1px solid var(--hair);
  color:var(--ink-muted);text-wrap:pretty}

/* ==========================================================================
   Artikel — the reading page
   One column, measured in ch like the rest of the site. Nothing here is a new
   colour or a new ground; an article is a page-head plus prose.
   ========================================================================== */
/* SAMMA MÅTT SOM TEXTEN, inte hela containern. Omslaget låg på 1124 px medan
   brödtexten och diagrammet under den ligger på 68ch, alltså 694. Bilden var
   därmed 62 % bredare än allt annat i artikeln och tog över uppslaget; det var
   den Julius kallade för stor.

   Noten som stod här sa att artikeln ska öppna med sin bild och inte möta den
   halvvägs ned. Det gör den fortfarande — den ligger först och är fortfarande
   artikelns största objekt. Den delar bara spalt med texten nu, så artikeln har
   EN vänsterkant i stället för två. */
.article__cover{max-width:68ch;margin:0 0 var(--pad-sm)}
.article__cover img{display:block;width:100%;height:auto;
  border:1px solid var(--hair);border-radius:var(--radius)}
.article__cover figcaption{margin-top:12px;font-family:var(--fig);
  font-size:var(--fs-mono-sm);letter-spacing:.03em;color:var(--ink-faint);
  text-wrap:pretty}

.article{max-width:68ch}
.article > * + *{margin-top:24px}
.article h2{margin-top:48px;font-size:var(--fs-h2-sm);line-height:1.2;
  letter-spacing:-.02em}
.article h3{margin-top:36px;font-size:21px;letter-spacing:-.015em}
.article p{font-size:18px;line-height:1.7;color:var(--ink-muted);
  text-wrap:pretty}
.article p b,.article p strong{color:var(--ink);font-weight:600}
.article ul{display:grid;gap:12px;padding-left:22px}
.article li{font-size:18px;line-height:1.65;color:var(--ink-muted);
  text-wrap:pretty}
.article .shot{margin-top:36px}
.article figcaption{margin-top:10px;font-family:var(--fig);
  font-size:var(--fs-mono-sm);letter-spacing:.03em;color:var(--ink-faint);
  text-wrap:pretty}

.pull{margin:40px 0;padding:28px 32px;border-left:2px solid var(--accent);
  background-color:var(--brand-25);border-radius:0 var(--radius) var(--radius) 0}
.pull p{font-size:21px;line-height:1.45;color:var(--ink);letter-spacing:-.01em}
.pull cite{display:block;margin-top:14px;font-family:var(--fig);
  font-size:var(--fs-mono-sm);font-style:normal;letter-spacing:.04em;
  color:var(--ink-faint)}

.byline{display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;
  margin-top:28px;padding-top:20px;border-top:1px solid var(--hair);
  font-family:var(--fig);font-size:var(--fs-mono-sm);letter-spacing:.04em;
  color:var(--ink-faint)}
.byline b{font-family:var(--sans);font-size:15px;font-weight:600;
  letter-spacing:0;color:var(--ink)}

/* ==========================================================================
   Forms
   There was no <form> anywhere on the site — every "Boka gratis analys"
   button led to boka.html, which offered only tel: and mailto:. Someone
   reading at 22:00 against "Vardagar 9–18" had nothing to do but leave.
   Submission is handled by Netlify Forms, so this stays a static site.
   ========================================================================== */
.form{display:grid;gap:32px}
.form__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px 24px}
@media (max-width:700px){.form__grid{grid-template-columns:minmax(0,1fr)}}
.form__field{display:flex;flex-direction:column;gap:8px}
.form__field--wide{grid-column:1/-1}
.form__label{font-size:15px;font-weight:500}
.form__label em{font-style:normal;font-weight:400;color:var(--ink-faint)}
.input,.select,.textarea{
  width:100%;padding:12px 14px;
  border:1px solid var(--hair);border-radius:var(--radius-sm);
  background-color:var(--surface-raised);
  font-family:var(--sans);font-size:16px;color:var(--ink);
  transition:border-color .2s ease,box-shadow .2s ease;
}
.textarea{min-height:104px;resize:vertical}
.input:focus,.select:focus,.textarea:focus{
  outline:none;border-color:var(--blue);box-shadow:0 0 0 3px rgba(3,125,170,.16)}
.form__hint{font-size:14px;line-height:1.6;color:var(--ink-faint);text-wrap:pretty}
.fieldset{border:0;padding:0;margin:0}
.fieldset__legend{padding:0;margin-bottom:16px;font-size:15px;font-weight:500;color:var(--ink-faint)}
.check{display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:start;cursor:pointer}
.check input{margin:2px 0 0;width:18px;height:18px;flex:none;accent-color:var(--brand)}
.check span{font-size:15px;line-height:1.55;color:var(--ink-muted);text-wrap:pretty}
.form__actions{display:flex;flex-wrap:wrap;align-items:center;gap:16px 20px}
@media (max-width:860px){.houses{grid-template-columns:minmax(0,1fr);gap:20px}}

/* --- Embedded calculator -------------------------------------------------- */
.calc{border:1px solid var(--hair);border-radius:var(--radius);overflow:hidden;background:var(--surface-raised)}
.calc iframe{width:100%;min-height:760px;border:0;display:block}

/* --- Timeline (steg för steg) --------------------------------------------- */
.timeline li{display:grid;grid-template-columns:auto minmax(0,1fr);gap:32px;padding:40px 0;border-top:1px solid var(--hair)}
.timeline__num{font-family:var(--fig);font-size:var(--fs-num-lg);line-height:1;color:var(--hair);font-variant-numeric:tabular-nums}
.timeline h3{margin-bottom:12px}
.timeline ul{display:flex;flex-direction:column;gap:8px;margin-top:16px}
.timeline ul li{display:grid;grid-template-columns:auto 1fr;gap:12px;padding:0;border:0;color:var(--ink-muted)}
.timeline ul li::before{content:"—";color:var(--ink-faint)}
@media (max-width:640px){.timeline li{gap:16px;padding:28px 0}}

/* --- Nav dropdown --------------------------------------------------------- */
.site-nav li{position:relative}
.nav-more{
  display:inline-flex;align-items:center;gap:6px;padding:8px 0;border:0;background:none;
  font-family:var(--sans);font-size:14px;font-weight:500;color:var(--ink-muted);
  cursor:pointer;white-space:nowrap;
}
.nav-more:hover,.nav-more[aria-expanded="true"]{color:var(--ink)}
.nav-more__caret{width:9px;height:9px;flex:none;transition:transform .2s ease}
.nav-more[aria-expanded="true"] .nav-more__caret{transform:rotate(180deg)}
.site-header--overlay .nav-more{color:rgba(255,255,255,.8)}
.site-header--overlay .nav-more:hover,
.site-header--overlay .nav-more[aria-expanded="true"]{color:#fff}
.nav-menu{
  position:absolute;top:calc(100% + 10px);right:0;z-index:20;
  min-width:200px;padding:8px 0;
  background:var(--surface-raised);border:1px solid var(--hair);border-radius:var(--radius-sm);
  box-shadow:0 12px 32px rgba(18,25,29,.12);
  display:none;
}
/* The panel hangs 10px below the trigger. That strip belongs to nothing, so
   the pointer travelling into the menu left the <li> the hover is bound to
   and the menu shut in your face. This bridges it — invisible, and a
   descendant of the same <li>, so the hover never breaks. */
.nav-menu::before{content:"";position:absolute;left:0;right:0;top:-11px;height:11px}
.nav-menu[data-open="true"]{display:block}
.nav-menu a{display:block;padding:10px 18px;font-size:14px;font-weight:500;color:var(--ink-muted);text-decoration:none;white-space:nowrap}
.nav-menu a:hover{background:var(--surface);color:var(--ink)}
.nav-menu a[aria-current="page"]{color:var(--ink)}

/* --- Media slot (awaiting photography) ------------------------------------ */
/* The frame height lives here, on the shared class, so every brand card in a
   row is the same height whether it holds a logo chip or a product photo.
   It was an aspect-ratio, which sized each frame from its own content and let
   a logo card stand 40px taller than the photo cards beside it. */
.media-slot{
  --slot-h:clamp(170px,17vw,230px);
  --slot-pad:14px;
  display:grid;place-items:center;gap:6px;
  height:var(--slot-h);padding:var(--slot-pad);
  background-color:var(--surface);border-bottom:1px solid var(--hair);
  text-align:center;
}
.media-slot span{font-family:var(--sans);font-size:var(--fs-mono-sm);color:var(--ink-faint)}
/* The default slot holds a LOGO — small, desaturated, sitting back. */
.media-slot img{max-height:44px;width:auto;filter:grayscale(1);opacity:.55}

/* A product photo is the opposite: it should fill the frame and keep its
   colour. Without this modifier the shots render at 44px and 55% grey, which
   reads as a broken image rather than a product. The ground goes white so the
   cut-out backgrounds meet the frame seamlessly. */
/* Explicit height, not the inherited aspect-ratio, and the frame and the image
   are sized from the SAME two custom properties. Percentage heights refused to
   resolve against this grid frame, so the image is capped in absolute terms
   instead — no percentage, nothing to resolve, and the two can never disagree
   because there is only one source for the number. */
.media-slot--photo{background-color:var(--surface-raised)}
.media-slot--photo img{
  /* A portrait inverter rendered 410px tall beside a landscape battery at
     222px, stepping the whole row up and down. Capping the height in absolute
     units fixes every frame to the same box while each photo keeps its own
     aspect. max-height must be restated to beat the 44px logo rule above,
     which has identical specificity. */
  width:auto;height:auto;
  max-width:100%;
  max-height:calc(var(--slot-h) - var(--slot-pad) * 2);
  filter:none;opacity:1;
}

/* --- One installation, shown big -----------------------------------------
   Deliberately NOT a sixth brand card: a real photo of a finished job is not
   the same kind of thing as a product shot, and shrinking it into the 3-up
   grid wasted it. Portrait image, so it sits beside the text rather than
   above it. */
.install{display:grid;grid-template-columns:minmax(0,.85fr) minmax(0,1fr);
  gap:40px;align-items:center;margin:0}
.install__media{border:1px solid var(--hair);border-radius:var(--radius);
  overflow:hidden;background-color:var(--surface)}
.install__media img{display:block;width:100%;height:100%;aspect-ratio:4/5;
  object-fit:cover;object-position:center 46%}
.install__body{display:flex;flex-direction:column;gap:14px}
@media (max-width:860px){
  .install{grid-template-columns:minmax(0,1fr);gap:24px}
  .install__media img{aspect-ratio:3/2}
}

/* --- Brand cards (batterier / växelriktare) ------------------------------- */
.brands{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px}
.brand{display:flex;flex-direction:column;background:var(--surface-raised);border:1px solid var(--hair);border-radius:var(--radius);overflow:hidden}
.brand__body{display:flex;flex-direction:column;gap:8px;padding:24px}
.brand__tag{font-family:var(--sans);font-size:var(--fs-mono);color:var(--ink-faint)}
@media (max-width:900px){.brands{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:600px){.brands{grid-template-columns:minmax(0,1fr);gap:20px}}

/* --- Filter with house graphics ------------------------------------------- */
.filters--visual{align-items:stretch;gap:16px}
.filter--visual{
  display:flex;flex-direction:column;align-items:center;gap:10px;
  width:132px;padding:14px 12px;border-radius:var(--radius);
  text-transform:none;letter-spacing:.02em;
}
.filter--visual img{width:100%;height:56px;object-fit:contain}
.filter--visual .filter__glyph{
  display:grid;place-items:center;width:100%;height:56px;
  font-family:var(--fig);font-size:20px;color:var(--ink-faint);
}
.filter--visual[aria-pressed="true"] img{filter:brightness(0) invert(1)}
.filter--visual[aria-pressed="true"] .filter__glyph{color:var(--surface)}
@media (max-width:640px){
  .filter--visual{width:calc(50% - 8px)}
  .filter--visual img,.filter--visual .filter__glyph{height:44px}
}

/* --- Production data ------------------------------------------------------ */
.prod-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:32px}
.prod{padding-top:24px;border-top:1px solid var(--hair)}
.prod__val{font-family:var(--fig);font-size:var(--fs-num-lg);font-weight:500;line-height:1;letter-spacing:-.03em;color:var(--blue);font-variant-numeric:tabular-nums}
.prod__lbl{margin-top:10px;font-family:var(--sans);font-size:var(--fs-mono);color:var(--ink-faint)}
.prod p{margin-top:8px;font-size:16px;color:var(--ink-muted);text-wrap:pretty}
@media (max-width:900px){.prod-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:560px){.prod-grid{grid-template-columns:minmax(0,1fr);gap:20px}}

/* ==========================================================================
   Start page — scroll-aware header, explorer, stepper, image slots
   ========================================================================== */

/* --- Sticky overlay header ------------------------------------------------ */
.site-header--overlay{
  position:fixed;top:0;left:0;right:0;z-index:100;
  transition:background-color .3s ease,border-color .3s ease,box-shadow .3s ease;
}
/* max-height är taket för hopfällningen när huvudet fastnar, och overflow är
   gömd för att animeringen ska klippa rent. Taket måste därför vara minst så
   högt som raden faktiskt är — annars klipps den hela tiden, inte bara under
   animeringen.

   38 px stämde mot .utility__inner{min-height:38px}. Men under 760 px höjs den
   raden till 44 px, så att betyget och telefonnumret blir tumvänliga
   träffytor, och taket följde inte med. Följden: sex pixlar av "4,6 av 5 på
   Google" var bortklippta på varje telefon, hela tiden, på varje sida med
   genomskinligt huvud. Julius såg det med menyn öppen, för då blir huvudet
   mörkt och underkanten syns.

   Höjden står nu i en variabel som båda reglerna läser, så de inte kan glida
   isär igen. Ändras radens höjd ändras taket med den. */
.site-header--overlay{--utility-h:38px}
.site-header--overlay .utility{
  overflow:hidden;
  transition:max-height .3s ease,opacity .25s ease,border-color .3s ease;
  max-height:var(--utility-h);opacity:1;
}
.site-header--overlay .site-header__inner,
.site-header--overlay .site-header__logo img,
.site-header--overlay .site-nav > ul > li > a,
.site-header--overlay .nav-more,
.site-header--overlay .site-header__cta{transition:all .3s ease}

/* Over the hero photo the CTA is white; once the bar goes solid it turns
   yellow — same .3s curve as the logo swap, so they change together. */
.site-header--overlay .site-header__cta{background:var(--surface-raised);color:var(--ink)}
.site-header--overlay .site-header__cta:hover{background:#e7eae9;color:var(--ink)}
.site-header--overlay[data-stuck="true"] .site-header__cta{background:var(--accent);color:var(--ink);box-shadow:none}
.site-header--overlay[data-stuck="true"] .site-header__cta:hover{background:var(--accent-hover);color:var(--ink)}

/* HÄR LÅG EN PIXELS SNEDVADDERING, padding-top 13 mot padding-bottom 11, för
   att knappens text skulle möta menyns baslinje. Borttagen på Julius besked:
   texten ska vara lodrätt centrerad I KNAPPEN, vilket är ett annat mål än att
   linjera mot något utanför den.

   Den var dessutom överspelad. Noten som stod här sa uttryckligen att
   kompensationen var bunden till paret 14/16 px och skulle mätas om ifall
   någon storlek ändrades. Typsnittet byttes till Geist, som har egna
   mått, och då stämde den inte längre.

   Uppmätt i Geist 600/16 med line-height 19,2 i en knapp på 45 px:

     13/11   bläcket 16,6 px över och 14,5 under, alltså 2,1 px lågt
     12/12   bläcket 15,6 över och 15,5 under

   Versalbandet ensamt låg bäst på 13/11, men det är inte det ögat väger:
   strängen är till största delen gemener, och både hela bläcket och
   x-höjdsbandet centreras av den symmetriska vadderingen.

   Regeln är alltså borta helt, så .btn--xs 12px gäller. Lägg inte tillbaka en
   snedvaddering utan att mäta om i det typsnitt som faktiskt används. */

.site-header--overlay[data-stuck="true"]{
  background:var(--surface-raised);
  border-bottom:1px solid var(--hair);
  box-shadow:0 1px 24px rgba(18,25,29,.06);
}
.site-header--overlay[data-stuck="true"] .utility{max-height:0;opacity:0;border-bottom-color:transparent}
.site-header--overlay[data-stuck="true"] .site-header__inner{min-height:68px}
.site-header--overlay[data-stuck="true"] .site-header__logo--light{display:none}
.site-header--overlay[data-stuck="true"] .site-header__logo--dark{display:block}
.site-header--overlay[data-stuck="true"] .site-header__logo img{width:150px}
.site-header--overlay[data-stuck="true"] .site-nav > ul > li > a,
.site-header--overlay[data-stuck="true"] .nav-more{color:var(--ink-muted)}
.site-header--overlay[data-stuck="true"] .site-nav > ul > li > a:hover,
.site-header--overlay[data-stuck="true"] .site-nav > ul > li > a[aria-current="page"],
.site-header--overlay[data-stuck="true"] .nav-more:hover{color:var(--ink)}
.site-header--overlay[data-stuck="true"] .nav-toggle{border-color:var(--hair);color:var(--ink)}
@media (max-width:1100px){
  .site-header--overlay[data-stuck="true"] .site-header__inner{border-bottom:0}
  .site-header--overlay[data-stuck="true"] .site-header__logo img{width:128px}
}

/* --- Image slot (awaiting photography) ------------------------------------ */
.shot{
  position:relative;display:grid;place-items:center;
  background:var(--surface-sunken);border:1px solid var(--hair);border-radius:var(--radius);
  overflow:hidden;aspect-ratio:4/3;
}
.shot--wide{aspect-ratio:16/9}
.shot--tall{aspect-ratio:3/4}
/* Portrait product shot that fills its frame: 4:5 box + a bottom-anchored
   cover crop, so the ~17% that gets cut all comes off the top. */
.shot--product{aspect-ratio:4/5}
.shot--product img{width:100%;height:100%;object-fit:cover;object-position:center 100%}
.shot img{width:100%;height:100%;object-fit:cover}
.shot__hint{display:flex;flex-direction:column;gap:6px;align-items:center;
  padding:24px;text-align:center;
  font-family:var(--sans);font-size:var(--fs-mono-sm);
  color:var(--ink-faint)}
.shot__hint b{font-weight:500;color:var(--ink-muted)}
.shot__hint svg{width:26px;height:26px;opacity:.4;margin-bottom:2px}

/* --- Explorer (analysis) -------------------------------------------------- */
/* ==========================================================================
   Analysen — pointer-driven, nothing collapsed
   Two earlier attempts and what each got wrong:

   The tab widget collapsed each row's paragraph to max-height:0 until selected,
   so every item needed a title for the closed state, a paragraph for the open
   one AND a figcaption — three slots, two of them restating each other. The
   duplication was structural, not sloppy writing.

   A 2x2 grid fixed the copy but threw away the interaction and took twice the
   height.

   So: keep the interaction, never collapse the text. All four descriptions are
   readable at rest; only the photo changes. The active row is marked by an
   accent rule and its number, never by hiding or dimming its words. Hover moves
   it on a pointer, click on touch, arrow keys on a keyboard — one text per item
   either way.
   ========================================================================== */
.analys{display:grid;grid-template-columns:minmax(0,.95fr) minmax(0,1.05fr);
  gap:56px;align-items:center}

.analys__list{display:flex;flex-direction:column}
.analys__row{
  position:relative;display:grid;grid-template-columns:minmax(0,1fr);
  gap:18px;align-items:start;width:100%;
  padding:20px 0 20px 22px;
  border:0;border-top:1px solid var(--hair);
  background:transparent;text-align:left;cursor:pointer;font:inherit;
}
.analys__row:last-child{border-bottom:1px solid var(--hair)}
/* the moving indicator — scaleY so it grows from the top rather than jumping */
.analys__row::before{
  content:"";position:absolute;left:0;top:18px;bottom:18px;width:2px;
  background-color:var(--accent);
  transform:scaleY(0);transform-origin:top;
  transition:transform .3s var(--ease-out);
}
.analys__row[aria-selected="true"]::before{transform:scaleY(1)}
.analys__row:focus-visible{outline:2px solid var(--blue);outline-offset:2px}


/* Title and description keep their colour whatever is selected. Dimming the
   inactive rows would make three quarters of the section harder to read, which
   is the problem this markup exists to avoid. */
.analys__copy{display:flex;flex-direction:column;gap:5px}
.analys__title{font-size:19px;font-weight:600;letter-spacing:-.015em;color:var(--ink)}
.analys__desc{font-size:15px;line-height:1.55;color:var(--ink-muted);text-wrap:pretty}

.analys__stage{
  position:relative;aspect-ratio:4/3;overflow:hidden;
  border:1px solid var(--hair);border-radius:var(--radius);
  background-color:var(--surface);box-shadow:var(--shadow-sm);
}
.analys__frame{
  position:absolute;inset:0;margin:0;opacity:0;pointer-events:none;
  transition:opacity .4s ease;
}
.analys__frame[data-open="true"]{opacity:1}
.analys__frame img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  transform:scale(1.03);transition:transform .7s var(--ease-out);
  /* photos stay on the compositor layer — see the rasterisation block above */
  backface-visibility:hidden;
}
.analys__frame[data-open="true"] img{transform:scale(1)}
/* the portrait battery shot: fill the frame with a blurred copy of itself
   instead of cropping the stack in half */
.analys__frame img.analys__blur{
  transform:scale(1.15);filter:blur(26px) saturate(1.1);opacity:.85;transition:none;
}
.analys__frame img:not(.analys__blur):last-child{object-fit:contain}

@media (max-width:900px){
  .analys{grid-template-columns:minmax(0,1fr);gap:32px}
  .analys__stage{order:-1}
}
@media (prefers-reduced-motion:reduce){
  .analys__row::before,.analys__frame,.analys__frame img{transition:none}
  .analys__frame img{transform:none}
}

.explorer{display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);gap:64px;align-items:start}
.explorer__list{border-top:1px solid var(--hair)}
.explorer__trigger{
  position:relative;display:block;width:100%;
  padding:22px 0 22px 22px;border:0;border-bottom:1px solid var(--hair);
  background:none;text-align:left;cursor:pointer;
  font-family:var(--sans);color:var(--ink-faint);
  transition:color .2s ease;
}
.explorer__trigger::before{
  content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);
  width:3px;height:0;background:var(--accent);transition:height .25s ease;
}
.explorer__trigger:hover{color:var(--ink-muted)}
.explorer__trigger[aria-selected="true"]{color:var(--ink)}
.explorer__trigger[aria-selected="true"]::before{height:60%}
.explorer__trigger h3{font-size:var(--fs-h3);font-weight:600;letter-spacing:-.02em}
.explorer__trigger p{
  max-height:0;overflow:hidden;opacity:0;
  font-size:16px;color:var(--ink-muted);
  transition:max-height .3s ease,opacity .25s ease,margin-top .3s ease;
}
.explorer__trigger[aria-selected="true"] p{max-height:200px;opacity:1;margin-top:8px}
.explorer__panel{display:none}
.explorer__panel[data-open="true"]{display:block;animation:fadeUp .35s ease both}
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.explorer__panel figcaption{
  margin-top:16px;font-family:var(--fig);font-size:var(--fs-mono);
  letter-spacing:.04em;color:var(--ink-faint);
}
@media (max-width:900px){
  .explorer{grid-template-columns:minmax(0,1fr);gap:32px}
  .explorer__media{order:-1}
}
@media (prefers-reduced-motion:reduce){
  .explorer__panel[data-open="true"]{animation:none}
}

/* --- Reasons (staggered, no numbering) ------------------------------------ */
.reasons{display:flex;flex-direction:column;gap:0}
.reason{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:56px;align-items:center;padding:56px 0;border-top:1px solid var(--hair);
}
.reason:last-child{border-bottom:1px solid var(--hair)}
.reason--flip .reason__body{order:2}
.reason__body{display:flex;flex-direction:column;gap:14px;align-items:flex-start}
.reason__tag{display:inline-flex;padding:5px 12px;border:1px solid var(--hair);border-radius:999px;
  font-family:var(--sans);font-size:var(--fs-mono-sm);
  color:var(--ink-muted);background:var(--surface-raised)}
.reason--plain{grid-template-columns:minmax(0,1fr);gap:0}
.reason--plain .reason__body{max-width:72ch}
@media (max-width:860px){
  .reason,.reason--flip{grid-template-columns:minmax(0,1fr);gap:24px;padding:40px 0}
  .reason--flip .reason__body{order:0}
}

/* --- Inline checklist ----------------------------------------------------- */
.inline-check{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 40px}
.inline-check li{display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:baseline;color:var(--ink-muted)}
.inline-check svg{width:15px;height:15px;flex:none;transform:translateY(2px)}
@media (max-width:640px){.inline-check{grid-template-columns:minmax(0,1fr)}}

/* --- Stepper (process) ---------------------------------------------------- */
.stepper__track{display:flex;gap:0;border-bottom:1px solid var(--hair);overflow-x:auto;scrollbar-width:none}
.stepper__track::-webkit-scrollbar{display:none}
.step-btn{
  position:relative;flex:1 0 auto;min-width:150px;
  padding:0 24px 20px 0;border:0;background:none;text-align:left;cursor:pointer;
  font-family:var(--sans);color:var(--ink-faint);transition:color .2s ease;
}
.step-btn:hover{color:var(--ink-muted)}
.step-btn[aria-selected="true"]{color:var(--ink)}
.step-btn::after{
  content:"";position:absolute;left:0;right:24px;bottom:-1px;height:2px;
  background:var(--accent);transform:scaleX(0);transform-origin:left;
  transition:transform .3s ease;
}
.step-btn[aria-selected="true"]::after{transform:scaleX(1)}
.step-btn .step-btn__lbl{display:block;font-size:var(--fs-h3);font-weight:600;letter-spacing:-.02em}
.step-btn .step-btn__meta{display:block;margin-top:6px;font-family:var(--sans);font-size:var(--fs-mono-sm);color:var(--ink-faint)}
.stepper__panels{padding-top:48px}
.stepper__panel{display:none}
.stepper__panel[data-open="true"]{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:56px;align-items:center;animation:fadeUp .35s ease both}
.stepper__panel h3{margin-bottom:14px}
@media (max-width:860px){
  .stepper__panel[data-open="true"]{grid-template-columns:minmax(0,1fr);gap:24px}
  .stepper__panels{padding-top:32px}
  .step-btn{min-width:132px;padding-right:20px}
  .step-btn::after{right:20px}
}
@media (prefers-reduced-motion:reduce){.stepper__panel[data-open="true"]{animation:none}}

/* --- Big figure moment ---------------------------------------------------- */
/* The figure column is `auto`, not `minmax(0,1fr)`. A 0 minimum lets the track
   be narrower than its own content, and the number is wider than half the
   container at every desktop width: --measure caps the container at 1180px, so
   the column stops growing at 514px while the font-size clamp keeps going to
   its 112px ceiling — about 564px of digits. The overflow silently ate the
   56px gap and left 6px between the number and the paragraph. Sizing the
   track to the content means the gap is always the gap. */
.bignum{
  display:grid;grid-template-columns:auto minmax(0,1fr);gap:56px;align-items:end;
  padding:48px 0;border-top:1px solid var(--brand);border-bottom:1px solid var(--hair);
}
.bignum__fig{
  font-family:var(--fig);font-weight:500;line-height:.92;letter-spacing:-.04em;
  font-size:clamp(56px,calc(28.3px + 7.09vw),112px);
  color:var(--blue);font-variant-numeric:tabular-nums;
  /* Reserve the final width up front. [data-count] animates 0 → 12 787, and an
     auto-sized track re-measures on every frame — the paragraph beside it would
     slide sideways for the whole count. This is 9 glyphs ("12 787 kr") of
     tabular mono at 0.6em advance, less the -.04em tracking that 1ch does not
     include, plus a hair of slack so the reserved width always wins over
     content measurement. Widen it if the figure ever gains a digit. */
  min-width:calc(9ch - 0.3em);
}
.bignum__lbl{margin-bottom:12px}
/* Stacks at 1000px, not 860px. Between those two the container is shrinking
   while the clamp still returns ~92–99px of type, so the two-column layout had
   even less room there than on a wide screen — the columns overlapped rather
   than merely crowding. */
@media (max-width:1000px){.bignum{grid-template-columns:minmax(0,1fr);gap:20px;align-items:start}}

/* --- Portrait media in a landscape frame ----------------------------------
   Product shots come in tall. Cropping them to 4:3 cuts the top or bottom off
   the unit, so instead the sharp image is contained in full and a blurred,
   desaturated copy of itself fills the frame behind it. Keeps every slot the
   same height (no layout jump when switching explorer panels) while showing
   the whole product. Works for any portrait image without per-image tuning. */
.shot--fit{display:block;position:relative;overflow:hidden}
.shot--fit .shot__bg{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;transform:scale(1.2);
  filter:blur(30px) saturate(.55) brightness(1.04);
  opacity:.5;
}
.shot--fit::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(120% 90% at 50% 45%,transparent 40%,rgba(18,25,29,.07) 100%);
}
.shot--fit .shot__fg{
  position:absolute;z-index:1;
  top:50%;left:50%;transform:translate(-50%,-50%);
  width:auto;height:auto;
  max-width:calc(100% - 24px);max-height:calc(100% - 20px);
  object-fit:contain;
  filter:drop-shadow(0 18px 40px rgba(18,25,29,.22));
}
@media (max-width:640px){.shot--fit .shot__fg{max-height:calc(100% - 14px)}}

/* ==========================================================================
   Motion & depth
   Hero runs a timed entrance choreography (staggered rise, easeOut-ish curve);
   everything below the fold reveals on scroll via IntersectionObserver.
   All of it collapses to "already visible" under prefers-reduced-motion.
   ========================================================================== */

:root{
  --ease-out:cubic-bezier(.16,1,.3,1);
  --shadow-sm:0 1px 2px rgba(18,25,29,.04), 0 10px 26px -14px rgba(18,25,29,.14);
  --shadow-md:0 2px 6px rgba(18,25,29,.05), 0 22px 48px -20px rgba(18,25,29,.26);
}

/* --- Hero entrance -------------------------------------------------------- */
@keyframes enterUp{from{opacity:0;transform:translateY(26px)}to{opacity:1;transform:none}}
@keyframes enterDown{from{opacity:0;transform:translateY(-18px)}to{opacity:1;transform:none}}
@keyframes enterFade{from{opacity:0}to{opacity:1}}

/* both, INTE forwards, och det är skillnaden mellan en glidning och ett ryck.

   forwards fyller bara EFTER att animeringen tagit slut. Under fördröjningen
   gäller alltså inga animeringsvärden alls, utan elementets egna: transform
   none, alltså slutläget. När fördröjningen går ut startar animeringen på sitt
   from-läge och elementet hoppar 26 px NEDÅT i en bildruta, för att sedan glida
   upp igen.

   På resten av sajten syntes det inte, för där är opacity 0 under
   fördröjningen och hoppet sker i osynlighet. I heron är opacity 1 hela tiden,
   satt av LCP-fixen, så där skedde det i full dager: fem element som klickade
   på plats i tur och ordning. Uppmätt på utlagd draft, ett språng nedåt per
   element vid 262, 370, 478, 587 och 704 ms, alltså exakt när respektive
   fördröjning gick ut.

   both = backwards + forwards. backwards lägger from-läget PÅ under
   fördröjningen, så elementet står på translateY(26px) från första bildrutan
   och det enda som händer sedan är glidningen. */
[data-enter]{opacity:0;animation:enterUp .85s var(--ease-out) both;
  animation-delay:calc(var(--i,0) * 180ms + 200ms)}
[data-enter="down"]{animation-name:enterDown}

/* ══ HERO-INNEHÅLLET TONAR INTE IN. ══════════════════════════════════════
   Google PageSpeed svarade NO_LCP på startsidan, och med den föll varje
   mätning som hänger på Largest Contentful Paint: LCP, Total Blocking Time
   och hela prestandapoängen redovisades som "Error!". Ingen annan sida hade
   felet — produkter.html rapporterade LCP efter 80 ms i samma mätning.

   TVÅ ORSAKER, och båda måste bort för att en kandidat ska finnas kvar:

   1. Chrome räknar aldrig ett element som LCP-kandidat om det har opacity 0
      när det målas, och omprövar det inte när opaciteten ändras. Hero-rubriken
      och allt annat i bandet bär [data-enter], som startar på 0 och dessutom
      väntar (--i × 180 + 200) ms innan animeringen ens börjar — 560 ms för
      rubriken. Under hela den tiden finns ingen textkandidat.

   2. Fotot bakom är inget alternativ: det ligger med inset:0 i ett band som är
      100svh, alltså täcker det hela fönstret, och en bild som fyller hela
      viewporten behandlas som bakgrund och utesluts. Det är därför bandet inte
      räddas av bilden när texten är osynlig.

   Mätt, samma sida och samma fönster: som förut → ingen LCP alls. Med
   [data-enter] på opacity 1 → LCP på H1 efter 120 ms. Med bandet krympt till
   80vh → LCP på fotot efter 124 ms.

   Rättningen är den första: hero-innehållet behåller sin entré men gör den med
   transform i stället för opacity. Rörelsen är densamma, rubriken är målad från
   första bildrutan, och kandidaten finns. Resten av sajten rör vi inte —
   ingen annan sida saknar LCP, och tonandet är sajtens uttryck. */
.hero [data-enter]{opacity:1;animation-name:enterUpFast}
/* TÄTARE TRAPPA I HERON. Grundvärdet är 180 ms per steg, satt för avsnitt där
   raderna ligger tätt. I heron är de fem delarna utspridda över hela bilden,
   och med 180 ms startade märkesraden 900 ms efter bevisraden och rörde sig
   ensam långt från allt annat. Fem rörelser i rad läses inte som en rörelse.
   110 ms håller ihop dem: hela sviten är över på 1,3 s i stället för 1,95. */
.hero [data-enter]{animation-delay:calc(var(--i,0) * 110ms + 160ms)}
@keyframes enterUpFast{from{transform:translateY(26px)}to{transform:none}}
@media (prefers-reduced-motion:reduce){
  .hero [data-enter]{animation:none;transform:none}
}
[data-enter="fade"]{animation-name:enterFade;animation-duration:1.1s}
/* HÄR LÅG .hero{animation:enterFade 1s ease-out both}, och den tog bort
   startsidans Largest Contentful Paint helt.

   Chrome räknar aldrig ett element som LCP-kandidat om det eller någon av dess
   föräldrar har opacity 0 när det målas, och det omprövar inte elementet när
   opaciteten senare ändras — kandidaten sätts vid FÖRSTA målningen. .hero
   omslöt både fotot och rubriken, alltså sidans två största objekt, och
   animerade från opacity 0 med fill-mode both. Följden: PageSpeed Insights
   svarade NO_LCP på startsidan, och varje mätning som hänger på LCP föll med
   den — Largest Contentful Paint, Total Blocking Time och hela
   prestandapoängen redovisades som "Error!".

   Mätt: startsidan gav NOLL LCP-poster, produkter.html gav en efter 76 ms.
   Skillnaden var precis den här raden.

   Ingen ersättningsanimation behövs. Barnen bär redan [data-enter] med
   förskjuten start, så hero har sin entré; behållarens egen toning låg ovanpå
   den och gjorde samma sak en gång till. Fotot tonar inte längre in utan står
   där direkt, vilket dessutom är snabbare upplevt. */

/* --- Scroll reveal -------------------------------------------------------- */
/* Gated on .js (set inline in <head>): with scripting off or broken, the
   content must never be left permanently invisible. */
.js [data-reveal]{
  opacity:0;transform:translateY(26px);
  transition:opacity .75s var(--ease-out),transform .75s var(--ease-out);
  transition-delay:calc(var(--d,0) * 90ms);
  will-change:opacity,transform;
}
.js [data-reveal].is-in{opacity:1;transform:none;will-change:auto}
.js [data-reveal="scale"]{transform:translateY(26px) scale(.97)}
.js [data-reveal="scale"].is-in{transform:none}

/* --- Logo marquee --------------------------------------------------------- */
.marquee{
  overflow:hidden;flex:1;min-width:0;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
}
.marquee__track{display:flex;align-items:center;gap:56px;width:max-content;animation:marquee 34s linear infinite}
.marquee:hover .marquee__track,.marquee:focus-within .marquee__track{animation-play-state:paused}
@keyframes marquee{to{transform:translateX(-50%)}}
@media (max-width:900px){.marquee__track{gap:36px;animation-duration:24s}}

/* --- Depth: cards lift ---------------------------------------------------- */
.card,.brand,.house,.video-card,.case,.contact-card,.freq,.figure-card,.calc,.map-embed{
  box-shadow:var(--shadow-sm);
  transition:box-shadow .35s var(--ease-out),transform .35s var(--ease-out);
}
.brand:hover,.house:hover,.video-card:hover,.contact-card:hover{
  box-shadow:var(--shadow-md);transform:translateY(-4px);
}
.case{transition:box-shadow .35s var(--ease-out)}
.case:hover{box-shadow:var(--shadow-md)}
.tile{transition:border-color .3s ease}
.tile:hover{border-top-color:var(--brand)}

/* --- Depth: media ---------------------------------------------------------- */
.shot{box-shadow:var(--shadow-sm)}
.shot img:not(.shot__bg){transition:transform .6s var(--ease-out)}
/* .shot__fg is centred with a translate; scaling it must preserve that or the
   image leaves the frame entirely. Plain <img> children have no translate. */
.reason:hover .shot img:not(.shot__bg):not(.shot__fg),
.explorer__media:hover .shot img:not(.shot__bg):not(.shot__fg){transform:scale(1.035)}
.reason:hover .shot__fg,
.explorer__media:hover .shot__fg{transform:translate(-50%,-50%) scale(1.03)}
.gallery figure{overflow:hidden;box-shadow:var(--shadow-sm);transition:box-shadow .35s var(--ease-out)}
.gallery figure img{transition:transform .6s var(--ease-out)}
.gallery figure:hover{box-shadow:var(--shadow-md)}
.gallery figure:hover img{transform:scale(1.05)}

/* --- Depth: tonal ground -------------------------------------------------- */
/* The old #fff → #fcfdfd ramp is gone: it was a 1% shift nobody could see, and
   as a shorthand it blocked the glow. Depth on white now comes from the glow. */
.section--white{background-color:var(--surface-raised)}
.section--dark{position:relative;overflow:hidden}
.section--dark>*:not(.deco):not(.tilebg){position:relative;z-index:1}

/* --- Depth: buttons ------------------------------------------------------- */
.btn--primary{box-shadow:0 1px 2px rgba(1,44,60,.14)}
.btn--primary:hover{box-shadow:0 2px 4px rgba(1,44,60,.18);transform:translateY(-1px)}
.btn--primary:active{transform:translateY(0)}
.btn__arrow{width:15px;height:15px;flex:none;transition:transform .3s var(--ease-out)}
.btn:hover .btn__arrow{transform:translate(3px,-3px)}
.link-arrow{transition:border-color .2s ease,gap .25s var(--ease-out)}
.link-arrow:hover{gap:12px}

/* --- Big number count-up -------------------------------------------------- */
.bignum__fig{font-variant-numeric:tabular-nums;transition:none}

@media (prefers-reduced-motion:reduce){
  [data-enter],.hero{animation:none!important;opacity:1!important;transform:none!important}
  .js [data-reveal]{opacity:1!important;transform:none!important;transition:none!important}
  .marquee__track{animation:none}
  .card:hover,.brand:hover,.house:hover,.video-card:hover,.contact-card:hover,
  .btn--primary:hover{transform:none}
}

/* ==========================================================================
   Breaking up the grey run between "elbil" and "Kundrecensioner"
   Three different grounds in sequence: a saturated colour panel, an elevated
   paper card, then a textured band — so the eye gets a change every screen.
   ========================================================================== */

/* --- Statement pair ------------------------------------------------------- */
.statements{
  display:grid;grid-template-columns:1.12fr 1fr;gap:24px;
  padding-top:56px;border-top:1px solid var(--hair);
}
.statement{
  position:relative;overflow:hidden;border-radius:var(--radius);
  padding:clamp(28px,3.2vw,48px);
  display:flex;flex-direction:column;gap:14px;align-items:flex-start;
}
.statement>*:not(.deco):not(.tilebg){position:relative;z-index:1}

/* The reference panel for the whole blue treatment. The warm glow itself is
   no longer declared here — it comes from the glow system further down, which
   every blue ground now shares. */
.statement--blue{background-color:var(--blue);color:#fff;box-shadow:var(--shadow-md)}
.statement--blue p{color:rgba(255,255,255,.86);text-wrap:pretty}
.statement--blue .reason__tag{
  background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.32);color:#fff;
}
.statement__ghost{
  right:.04em;bottom:-.30em;
  font-size:clamp(150px,19vw,260px);
  letter-spacing:-.06em;color:rgba(255,255,255,.11);
}

.statement--paper{background:var(--surface-raised);border:1px solid var(--hair);box-shadow:var(--shadow-sm)}

@media (max-width:860px){.statements{grid-template-columns:minmax(0,1fr);padding-top:40px}}

/* --- "Vad du får" lifted onto paper --------------------------------------- */
.getpanel{
  margin-top:24px;padding:clamp(28px,3vw,44px);
  background:var(--surface-raised);border:1px solid var(--hair);
  border-radius:var(--radius);box-shadow:var(--shadow-sm);
}

/* --- Reviews band --------------------------------------------------------- */
.reviews-band{
  position:relative;overflow:hidden;
  background:linear-gradient(180deg,#edf0f0 0%,#f2f4f4 55%,var(--surface) 100%);
}
.reviews-band::before{                       /* dot grid, faded out downward */
  content:"";position:absolute;inset:0;pointer-events:none;
  background-image:radial-gradient(rgba(18,25,29,.07) 1px,transparent 1px);
  background-size:24px 24px;
  -webkit-mask-image:linear-gradient(180deg,#000 0%,transparent 68%);
  mask-image:linear-gradient(180deg,#000 0%,transparent 68%);
}
.reviews-band>*:not(.deco):not(.tilebg){position:relative;z-index:1}
.reviews-band .reviews{border-top-color:rgba(2,77,104,.28)}
.reviews-band .reviews__nav button{background:var(--surface-raised)}

/* Oversized background glyph. .deco keeps it out of flow no matter which
   ground it sits on — see [data-glow]>*:not(.deco). */
.deco{position:absolute;z-index:0;pointer-events:none;user-select:none;line-height:1;font-weight:600}
.reviews-mark{
  top:-.34em;right:-.02em;
  font-size:clamp(200px,26vw,360px);
  color:rgba(10,75,119,.06);
}

.rating-badge{
  display:inline-flex;align-items:center;gap:10px;flex:none;
  padding:9px 16px;border-radius:999px;
  background:var(--surface-raised);border:1px solid var(--hair);
  box-shadow:var(--shadow-sm);font-size:15px;color:var(--ink-muted);
  white-space:nowrap;
}
.rating-badge b{color:var(--ink);font-weight:600}
@media (max-width:640px){.rating-badge{font-size:14px;padding:8px 13px}}

/* --- Footer: hierarchy, wrapping sitemap, social --------------------------- */
.site-footer__grid{grid-template-columns:1.15fr 1.35fr 1fr}
.site-footer h2{
  margin:0 0 20px;
  font-family:var(--sans);font-size:17px;font-weight:600;
  color:var(--ink);
}
/* 11 links in one column read as a wall — two columns halves the height */
.site-footer nav ul{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 28px}
.site-footer ul a{font-size:15px}

/* Two of these now sit together, so the top margin moves to the wrapper —
   otherwise they butt against each other and cannot wrap cleanly. */
/* A row of icons, not two stacked labelled buttons. In the footer these are the
   least important thing on the page and were taking 335x96px of it. The
   accessible name moved to aria-label, so a screen reader loses nothing. */
.socials{display:flex;flex-direction:row;align-items:center;gap:10px;margin-top:18px}
.social--icon{padding:9px;gap:0}
.social--icon svg{width:16px;height:16px}
.socials .social{margin-top:0}
.social{
  display:inline-flex;align-items:center;gap:9px;margin-top:22px;
  padding:9px 15px;border:1px solid var(--hair);border-radius:999px;
  font-size:15px;font-weight:500;color:var(--ink-muted);text-decoration:none;
  transition:border-color .2s ease,color .2s ease,background-color .2s ease,transform .2s var(--ease-out);
}
.social:hover{border-color:var(--brand);color:var(--brand);background:var(--surface-raised);transform:translateY(-1px)}
.social svg{width:17px;height:17px;flex:none}

@media (max-width:860px){
  .site-footer__grid{grid-template-columns:minmax(0,1fr)}
  .site-footer h2{font-size:16px}
}
@media (max-width:420px){
  .site-footer nav ul{grid-template-columns:minmax(0,1fr)}
}

/* --- Spec rows (feature + availability) -----------------------------------
   En specifikationslista, inte fyra små avsnitt. Raderna låg tidigare på
   26 px vaddering med rubriken i --fs-h3, alltså 24 px på skrivbord: samma
   grad som en avsnittsrubrik, satt på ordet "Dubbelglas". Fyra fakta tog
   542 px. Julius den 1 augusti: listan är enorm och ingenting motiverar den
   uppställningen.

   Graden bär hela problemet. Rubriken går till 17 px, samma som .dsheet__row
   dt två sektioner ned på samma sida, brödtexten en halv grad under
   löpande text. Då står de tre spalterna kvar på sin baslinje och läses som
   en tabell i stället för som en staplad rubrikserie. */
.specs{border-top:1px solid var(--brand)}
.specs__row{
  display:grid;grid-template-columns:minmax(0,12.5rem) minmax(0,1fr) auto;
  gap:28px;align-items:baseline;padding:13px 0;border-bottom:1px solid var(--hair);
  transition:background-color .25s ease;
}
.specs__row:hover{background:rgba(18,25,29,.015)}
.specs__row h4{font-size:17px;font-weight:600;letter-spacing:-.015em;line-height:1.45}
.specs__row p{font-size:15.5px;line-height:1.55;text-wrap:pretty}
.specs__avail{
  justify-self:end;padding:4px 11px;border-radius:999px;white-space:nowrap;
  font-size:13px;font-weight:500;border:1px solid var(--hair);
}
.specs__avail--all{background:rgba(10,75,119,.07);border-color:rgba(10,75,119,.22);color:var(--blue)}
.specs__avail--some{background:var(--surface);color:var(--ink-muted)}
@media (max-width:800px){
  .specs__row{grid-template-columns:minmax(0,1fr) auto;gap:6px 14px;padding:14px 0}
  /* Etiketten hör ihop med namnet, inte med slutet av brödtexten. Utan den
     uttryckliga placeringen hamnar den på en egen tredje rad, högerställd
     under stycket, eftersom stycket spänner över båda spalterna och knuffar
     ned den. Två rader i stället för tre. */
  .specs__row h4{grid-area:1/1}
  .specs__avail{grid-area:1/2;align-self:center}
  .specs__row p{grid-area:2/1/3/-1}
}

/* --- Photo mosaic --------------------------------------------------------- */
.mosaic{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:186px;gap:16px}
.mosaic figure{
  margin:0;overflow:hidden;border-radius:var(--radius);
  border:1px solid var(--hair);box-shadow:var(--shadow-sm);
  transition:box-shadow .35s var(--ease-out);
}
.mosaic figure:hover{box-shadow:var(--shadow-md)}
.mosaic img{width:100%;height:100%;object-fit:cover;transition:transform .6s var(--ease-out)}
.mosaic figure:hover img{transform:scale(1.05)}
.mosaic figure:nth-child(1){grid-column:span 2;grid-row:span 2}
.mosaic figure:nth-child(4){grid-column:span 2}
@media (max-width:860px){
  .mosaic{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:150px}
  .mosaic figure:nth-child(1){grid-column:span 2;grid-row:span 1}
  .mosaic figure:nth-child(4){grid-column:span 2}
}

/* --- Stat block ----------------------------------------------------------- */
/* .statblock var sifferrutan på produkter.html och hade en enda användning,
   modulverkningsgraden, som togs bort 1 augusti 2026. Hela familjen är borta
   med den. */

/* ==========================================================================
   Scroll journey (Steg för steg) — sticky media, scroll-driven progress rail
   ========================================================================== */
.journey__grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);gap:72px;align-items:start}
.journey__media{position:sticky;top:132px;display:grid;grid-template-columns:auto minmax(0,1fr);gap:28px}

.journey__rail{position:relative;width:2px;background:var(--hair);border-radius:2px;align-self:stretch;min-height:340px}
.journey__rail span{
  position:absolute;left:0;top:0;width:100%;background:var(--accent);border-radius:2px;
  transition:height .5s var(--ease-out);
}
.journey__rail b{
  position:absolute;left:50%;width:11px;height:11px;margin-left:-5.5px;border-radius:50%;
  background:var(--surface);border:2px solid var(--hair);
  transition:background-color .35s ease,border-color .35s ease,transform .35s var(--ease-out);
}
.journey__rail b.on{background:var(--accent);border-color:var(--accent);transform:scale(1.25)}

.journey__frame{position:relative;overflow:hidden;border-radius:var(--radius);
  border:1px solid var(--hair);box-shadow:var(--shadow-sm);aspect-ratio:4/3;background:var(--surface-sunken)}
.journey__frame img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:0;transform:scale(1.04);
  transition:opacity .6s var(--ease-out),transform .9s var(--ease-out);
}
.journey__frame img.on{opacity:1;transform:none}
.journey__cap{
  position:absolute;left:0;right:0;bottom:0;z-index:2;
  padding:22px 20px 16px;color:#fff;font-size:15px;font-weight:500;
  background:linear-gradient(180deg,transparent,rgba(1,44,60,.76));
}

.journey__steps{display:flex;flex-direction:column}
.jstep{padding:56px 0;border-bottom:1px solid var(--hair)}
.jstep:first-child{padding-top:0}
.jstep:last-child{border-bottom:0}
.jstep__tag{
  display:inline-flex;padding:5px 13px;margin-bottom:16px;border-radius:999px;
  border:1px solid var(--hair);background:var(--surface-raised);
  font-size:14px;font-weight:500;color:var(--ink-muted);
  transition:border-color .3s ease,color .3s ease,background-color .3s ease;
}
.jstep.on .jstep__tag{border-color:var(--brand);color:var(--brand)}
.jstep h3{margin-bottom:14px}
.jstep__list{display:flex;flex-direction:column;gap:10px;margin-top:20px}
.jstep__list li{display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:baseline;color:var(--ink-muted)}
.jstep__list svg{width:15px;height:15px;flex:none;transform:translateY(2px)}

@media (max-width:900px){
  .journey__grid{grid-template-columns:minmax(0,1fr);gap:32px}
  .journey__media{position:relative;top:0;grid-template-columns:minmax(0,1fr)}
  .journey__rail{display:none}
  .jstep{padding:36px 0}
}

/* ==========================================================================
   Track (startsidans process) — one unbroken rail, drawn by the scroll itself
   ==========================================================================
   The start page used to show this as a four-tab stepper: one description
   visible, three behind a click, and a grey placeholder box in every panel.
   That is the same mistake the Analysen section was rebuilt three times to fix
   (see README) — so here all four steps are readable at rest and there is
   nothing to click.

   The rail is the argument, not decoration. It runs unbroken from step one to
   step four, which is the page's actual promise: same projektledare the whole
   way, the one who calculates is the one who builds. A line that never breaks
   says that faster than the sentence under it does.

   Same vocabulary as the .journey rail on steg-for-steg.html — accent fill,
   hairline track, dots that light as the fill reaches them — so clicking
   through to the full page feels like the same object seen closer. The
   difference is that this one needs no JavaScript: the fill is driven by a
   scroll-linked animation off .track's own view timeline.

   Segments live on a real .track__line element rather than a pseudo, because a
   pseudo-element cannot host the fill's own pseudo. Each segment spans its
   column plus the gap, so it lands exactly on the next dot at any width and the
   last step simply omits it — no magic percentage to re-derive if a step is
   ever added or removed. */
.track{
  --gap:32px;
  --rail-y:8px;
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--gap);
  margin:0;padding:0;list-style:none;
}
/* Equal-height columns with the chip pushed to the bottom, so the four chips
   line up however many lines the copy above them runs to. The top padding is
   what the rail and its dot sit in, now that there is no number above them. */
.track__step{position:relative;display:flex;flex-direction:column;padding-top:24px}

/* The dot. Default state is lit: a browser without scroll-linked animations
   must show a finished rail, not an empty one. */
.track__step::before{
  content:"";position:absolute;left:0;top:var(--rail-y);z-index:2;
  width:13px;height:13px;margin-top:-6.5px;border-radius:50%;
  background-color:var(--accent);border:2px solid var(--accent);
  transition:transform .35s var(--ease-out);
}
.track__step:hover::before{transform:scale(1.3)}

.track__line{
  position:absolute;left:0;top:var(--rail-y);z-index:0;
  width:calc(100% + var(--gap));height:2px;margin-top:-1px;
  background-color:var(--hair);border-radius:2px;
}
.track__line::after{
  content:"";position:absolute;inset:0;border-radius:2px;
  background-color:var(--accent);transform-origin:left center;
}

/* --ink, not --ink-faint: without scroll-linked animation this is the state the
   page settles in, and a finished rail is the correct fallback. The animation
   below starts it faint and darkens it. (It also wins over :hover while it
   holds, which is why the number has no hover state — the tag and dot do.) */
/* Reuses the site's .shot component. 3:2 sits between the 16:9 the old stepper
   used and the 4:3 elsewhere — deep enough to read at a quarter of the
   container, shallow enough that four of them do not dominate the section.
   translateZ(0) promotes each to its own layer: the panel grids shimmer when the
   browser rasterises them inside a section that is scrolling and animating at
   the same time. Same fix as the gallery. */
.shot--step{aspect-ratio:3/2}
.track__step .shot{margin-bottom:20px}
.track__step .shot img{transform:translateZ(0)}

.track__title{margin-bottom:8px;font-size:19px;font-weight:600;line-height:1.3}
.track__body{margin-bottom:16px;font-size:15px;line-height:1.55;
  color:var(--ink-muted);text-wrap:pretty}
/* Not a button. It was a bordered pill, which on a card next to real buttons
   reads as clickable — so it is plain text with an accent dot instead. The dot
   keeps it findable without borrowing a control's affordance. */
.track__tag{
  display:inline-flex;align-items:center;gap:9px;align-self:flex-start;margin-top:auto;
  font-family:var(--sans);font-size:13px;font-weight:500;color:var(--ink-muted);
  transition:color .3s ease;
}
/* HÄR LÅG EN PRICK före taglinjen, 6 px i accentgult, senare märkesblått.
   Borttagen på Julius besked. Den satt kvar från när raden var en fri rad utan
   avgränsning och behövde något som markerade att den började. Sedan raden
   fick sin hårlinje gör linjen det jobbet, och pricken blev en andra markör
   för samma sak, framför en text som inte är en punktlista. */
.track__step:hover .track__tag{color:var(--ink)}
.track__foot{
  max-width:62ch;margin-top:var(--pad-sm);padding-top:24px;
  border-top:1px solid var(--hair);
  font-size:16px;line-height:1.6;color:var(--ink-muted);text-wrap:pretty;
}

/* Dots light just ahead of the fill that reaches them, so the eye is pulled
   left to right rather than watching four things happen at once. Ranges are in
   `cover`, off .track's own timeline: short element, predictable window, and it
   completes while the section is still comfortably on screen. */
/* HELFÄRGAD RÄLS, INTE EN SOM FYLLS PÅ. Här låg en scrollstyrd animering som
   drog fyllningen och tände noderna en i taget. Julius kommentar 10: den ser
   konstig ut och kan lika gärna vara hel färg över hela, alla fyra stegen.

   Det som blir kvar är precis det läge som redan var reservläget för
   webbläsare utan scroll-linked animations, och som noten vid .track__step
   beskriver: rälsen full och alla fyra noderna tända. Ingenting behövde
   läggas till för att få det — animeringen togs bort och grundläget står
   kvar. Hovringen på noden rörs inte; den var uttryckligen bra.

   view-timeline-name på .track och keyframesen trackFill, trackNode,
   trackFillY och trackNodeBrand är borttagna i samma veva. De hade inte gjort
   någon skada, men en tidslinje utan animeringar och fyra keyframes utan
   användare är precis den sortens rester som nästa läsare tror betyder något. */
/* Siffran tänds med sin nod. Färgen animeras i samma keyframe och inte i en
   egen regel, annars byter texten färg vid en annan tidpunkt än pillret den
   står i.
   Här låg tidigare ett löst "}to{color:var(--ink)}}" efter blockets slut,
   kvar sedan numret ovanför noden togs bort. Webbläsaren läste det som en
   regel för elementet <to> och slängde den, alltså utan verkan, men den var
   nästa redigerings fälla. */


/* Under 900px the rail turns vertical, which is also what the journey page
   does at this width. The segment expression is unchanged — dot to dot is
   still "this step plus the gap", just measured down instead of across. */
@media (max-width:900px){
  .track{grid-template-columns:minmax(0,1fr);--gap:36px;--rail-y:9px}
  .track__step{padding-left:34px;padding-top:0}
  .track__line{
    left:5.5px;top:var(--rail-y);margin-top:0;
    width:2px;height:calc(100% + var(--gap));
  }
  .track__line::after{transform-origin:center top}
  .track__foot{margin-top:36px}
}


/* --- Segment router band (leads existing owners to the battery page) ------- */
/* Samma botten som de andra två mosaikbanden: logotypblå --brand. --dark var
   den grafitton bandet hade innan det kom upp på rutnätet. */
.router{position:relative;overflow:hidden;background-color:var(--brand);color:var(--dark-fg)}
.router>*:not(.deco):not(.tilebg){position:relative;z-index:1}

/* Bakgrundslagren, samma sak som på .closer: .deco kan de inte bära, den är
   avstängd sedan vattenstämplarna ströks. Slöjan är tät där texten står och
   tunn åt höger, men aldrig noll: mosaiken ska vara en yta att läsa på, inte
   sidans huvudperson. */
.tilebg{position:absolute;inset:0;z-index:0;pointer-events:none}
/* En duk är ett ERSATT element, och för ett sådant sträcker inset:0 ingenting:
   med width:auto är det över­bestämt, right ignoreras och duken ritas ut i sin
   EGEN storlek, alltså attributen width/height som tiles.js sätter i
   enhetspixlar. På en skärm med dubbel upplösning blir bandet därför ritat i
   dubbel skala och plattorna dubbelt så stora, medan --t säger 15,65.
   .router__gl och .closer__gl bar de här två raderna var för sig, så de två
   ursprungliga banden såg riktiga ut och de tio band som fick duk den
   1 augusti gjorde det inte. Regeln hör hemma på duken, en gång. */
/* DUKEN BÄR NU EN BILD, inte en shader. Julius lämnade NewBackgroundStyle.png,
   en mjuk diagonal i turkos och blått, och den ersätter mosaiken bakom alla
   band med data-tiles: avslutet, "Var vill du börja?", router-bandet och de
   övriga på sajten.

   ELEMENTET ÄR KVAR MED FLIT. js/tiles.js mäter plattstorleken ur dukens egen
   rect, och all snappning av knappar och spalter hänger på den. Byter man ut
   <canvas> mot <img> eller lägger bilden på sektionen faller de måtten
   tillbaka på sektionsrutan, som är en annan låda, och knapparna hamnar en
   halv platta fel utan att något ser trasigt ut. En tom duk med en
   bakgrundsbild ritar exakt där shadern ritade.

   cover och center: bilden är 2400x784, alltså mycket bredare än hög, och
   banden varierar från ungefär 300 till 700 px höga. Med cover skalas den
   alltid till att fylla, och det är gradienten den ska visa, inte ett motiv,
   så beskärningen spelar ingen roll.

   --bandton är bilden nedtonad mot märkesblått innan bandens egna slöjor läggs
   på. Den behövs: bildens ljusaste parti ligger på luminans .50, och vit
   brödtext kräver att bakgrunden håller sig under .183 för att klara AA:s
   4,5:1. Utan tonen föll sex av elva textrutor på "Var vill du börja?" under
   kravet, uppmätt på renderade pixlar och inte uppskattat. Vid .44 klarar
   samtliga band på samtliga sidor kravet. Sänker man tonen syns bilden mer och
   texten blir svårare att läsa; det är samma reglage åt två håll.

   BYTS BILDEN MÅSTE FILNAMNET BYTAS. netlify.toml cachar /assets/* som
   immutable i ett år, så en ny fil på samma namn når inte den som varit här
   förut. Samma regel gäller alla bilder i mappen. */
canvas.tilebg{
  width:100%;height:100%;display:block;
  background-image:
    linear-gradient(rgba(2,77,104,var(--bandton,.44)), rgba(2,77,104,var(--bandton,.44))),
    url("../assets/img/bandbakgrund.webp");
  background-size:cover;background-position:center;
}
.router__gl{width:100%;height:100%;display:block}
.router__veil{
  background:linear-gradient(100deg,
    rgba(2,77,104,.88) 0%, rgba(2,77,104,.78) 26%, rgba(2,77,104,.56) 48%,
    rgba(2,77,104,.38) 68%, rgba(2,77,104,.32) 100%);
}

/* KOLUMNBREDDER, INDRAG OCH LODRÄTT UTRYMME SÄTTS AV js/tiles.js, i plattor.
   Värdena här gäller bara fram till första mätningen, och om skriptet aldrig
   kör. Bandet har alltså ingen .container: indraget räknas fram så att
   innehållsbredden blir ett helt antal plattor. */
/* Spaltbredderna och sidindraget sätts av js/tiles.js ur data-tile-fit och
   data-tile-split, direkt på elementet. Kvar här är reservvärdena och den
   lodräta vadderingen. */
.router__grid{
  display:grid;align-items:center;
  grid-template-columns:var(--col-a,minmax(0,1.05fr)) var(--col-b,minmax(0,1fr));
  column-gap:var(--col-gap,48px);
  padding-block:var(--pad-md);
  padding-inline:var(--gutter);
}
.router__body{display:flex;flex-direction:column;gap:18px;align-items:flex-start;justify-content:center}

/* Bildrutan: helt antal plattor bred och hög, plattans radie, hårstrecket
   liggande i fogen. Gradienten som förut smälte bilden mot bandets vänsterkant
   är borta med den, för rutan är en ruta nu och inte en utfallande bild. */
.router__media{
  position:relative;margin:0;
  width:100%;height:340px;
  border-radius:var(--tile-r,4px);overflow:hidden;
  /* Samma kant som ett glaskort: en ljus hårlinje runt om och en ljusare
     överkant, som på en glaskant. Bilden är täckande, så det finns ingen
     backdrop att sudda; det är ramen som gör att rutan hör ihop med korten i
     avsiktsväxeln och med den genomskinliga knappen. */
  box-shadow:
    0 0 0 1px rgba(255,255,255,.24),
    inset 0 1px 0 rgba(255,255,255,.26),
    0 26px 54px -30px rgba(1,20,28,.7);
}
.router__media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:38% 50%}
/* Färgen sätts längre ned, se .router.section--dark .router__eyebrow. */
.router__eyebrow{font-weight:600}

/* Knapparna får sin bredd av skriptet, i hela plattor, och en egen bredd var.
   Höjden och radien kommer ur samma rutnät. */
.router__acts{gap:var(--tile-gap,16px)}
.router .btn-row .btn{padding-block:0;border-radius:var(--tile-r,var(--radius-sm))}
/* Den genomskinliga knappen får glaset i regeln ovanför, tillsammans med
   avslutets. */
@media (max-width:900px){
  .router__grid{grid-template-columns:minmax(0,1fr);row-gap:var(--col-gap,32px)}
  .router__body{order:1}
  .router__media{order:2;margin:0}
  /* En lodrät slöja i stället för en vågrät: i en spalt står texten ovanför
     bilden, inte till vänster om den. */
  .router__veil{background:linear-gradient(180deg,
    rgba(2,77,104,.88) 0%, rgba(2,77,104,.78) 42%, rgba(2,77,104,.46) 100%)}
}

/* rule-list now carries check marks rather than 01/02 counters */
.rule-list svg{width:15px;height:15px;flex:none;transform:translateY(3px)}

/* --- Contrast pair (utan / med batteri) ----------------------------------- */
.versus{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:24px}
.versus__col{
  position:relative;overflow:hidden;border-radius:var(--radius);
  padding:clamp(28px,3vw,40px);display:flex;flex-direction:column;gap:16px;
}
.versus__col--bad{background:var(--surface-raised);border:1px solid var(--hair);box-shadow:var(--shadow-sm)}
.versus__col--good{background-color:var(--blue);color:#fff;box-shadow:var(--shadow-md)}
.versus__col--good .reason__tag{background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.32);color:#fff}
.versus__col>*:not(.deco):not(.tilebg){position:relative;z-index:1}
.versus ul{display:flex;flex-direction:column;gap:12px;margin-top:4px}
.versus li{display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:baseline}
.versus__col--bad li{color:var(--ink-muted)}
.versus__col--good li{color:rgba(255,255,255,.9)}
.versus__mark{width:16px;flex:none;font-weight:600;line-height:1.5}
.versus__col--bad .versus__mark{color:var(--ink-faint)}
.versus__col--good .versus__mark{color:var(--accent)}
@media (max-width:860px){.versus{grid-template-columns:minmax(0,1fr)}}

/* ==========================================================================
   Glow system
   The treatment from the "Elbil hemma" panel — a saturated ground with warm
   light bleeding across it — generalised. Direction, size and intensity are
   variables, so the same idea reads differently in each place it appears
   instead of turning into one repeated effect.

   Every blue ground is in the selector list below, so the glow is part of what
   "blue ground" means rather than something a page has to remember to switch
   on. The defaults ARE the elbil panel: 58% 88% at 14% 14%, amber at .22.
   [data-glow] only re-aims or re-tints it.

   One rule for the whole file: a ground sets background-COLOR, never the
   `background` shorthand. The shorthand resets background-image to none, and
   because every ground class used to sit later in the file than this block, it
   was silently wiping the glow off every blue surface in the site.
   ========================================================================== */
/* Varje sektion som ligger på rutnätet. overflow:hidden är inte kosmetik:
   duken är absolut och lika stor som sektionen, och justeringen skjuter
   innehåll upp till en halv platta. */
[data-tiles]{position:relative;overflow:hidden}
/* Standardslöjan för ett blått band på mosaiken. Tät upptill där rubriken
   står, tunnare nedåt, aldrig under 0,62: banden bär löpande text över hela
   bredden, till skillnad från avsiktsväxeln vars nedre halva är ljusa kort.
   Ett band med egen komposition, som avslutet och växeln, har sin egen slöja;
   det här är den som gäller när inget annat är sagt. */
.tileveil{
  background:linear-gradient(172deg,
    rgba(2,77,104,.90) 0%, rgba(2,77,104,.76) 45%, rgba(2,77,104,.64) 100%);
}

[data-glow],
.section--blue,.section--dark,.section--deep,.closer,.router,
.statement--blue,.versus__col--good,.card--dark{
  position:relative;
  background-image:radial-gradient(
    var(--glow-size,58% 88%) at var(--glow-x,14%) var(--glow-y,14%),
    rgba(var(--glow-rgb,255,192,0),var(--glow-a,.22)) 0%,
    transparent 68%);
  background-repeat:no-repeat;
}
/* Lift real content above the glow — but never restack the oversized
   decorative glyphs, which have to stay out of flow or they push the whole
   section down by their own line height. */
[data-glow]>*:not(.deco):not(.tilebg){position:relative;z-index:1}

/* Every blue ground states the full elbil recipe on itself rather than leaning
   on the var() fallbacks. The custom properties INHERIT, so a blue panel nested
   inside a section marked data-glow="cool …" would otherwise pick up the cool
   tint from its ancestor and glow blue-on-blue — which is exactly what happened
   to the elbil panel itself, sitting inside a "cool left faint" section. */
.section--blue,.section--dark,.section--deep,.closer,.router,
.statement--blue,.versus__col--good,.card--dark{
  --glow-rgb:255,192,0;
  --glow-a:.22;
  --glow-size:58% 88%;
  --glow-x:14%;
  --glow-y:14%;
}
/* mirrored so the pair of versus columns doesn't repeat the same corner */
.versus__col--good{--glow-x:86%}

/* anchor points — the gradient centre sits at an explicit spot in the box,
   so it can't drift off-centre the way an oversized ::before did.
   These come last so a data-glow on a blue ground can still re-aim it. */
[data-glow~="left"]  {--glow-x:14%;--glow-y:14%}
[data-glow~="right"] {--glow-x:86%;--glow-y:14%}
[data-glow~="top"]   {--glow-x:50%;--glow-y:-6%;--glow-size:70% 80%}
[data-glow~="bottom"]{--glow-x:50%;--glow-y:106%;--glow-size:70% 80%}
[data-glow~="corner"]{--glow-x:88%;--glow-y:88%}
[data-glow~="sweep"] {--glow-x:22%;--glow-y:112%;--glow-size:95% 78%}

[data-glow~="faint"] {--glow-a:.12}
[data-glow~="strong"]{--glow-a:.30}
[data-glow~="cool"]  {--glow-rgb:3,125,170;--glow-a:.10}
[data-glow~="cool"][data-glow~="strong"]{--glow-a:.18}

/* Blue ground, same family as the elbil panel but at section scale */
/* --blue är #024D68, exakt mosaikens mellanton, så ett blått band med duk
   och ett utan står på samma färg: duken är en textur ovanpå, inte en ny
   botten, och reservläget utan WebGL är bandet som det alltid sett ut. */
.section--blue{
  background-color:var(--blue);color:#fff;
  position:relative;overflow:hidden;
}
.section--blue>*:not(.deco):not(.tilebg){position:relative;z-index:1}
.section--blue .eyebrow{color:rgba(255,255,255,.72)}
.section--blue .muted,.section--blue p{color:rgba(255,255,255,.86)}
.section--blue .rule-list li{border-color:rgba(255,255,255,.2)}
.section--blue .rule-list li:last-child{border-bottom-color:rgba(255,255,255,.2)}
.section--blue .btn--primary{background:#fff;color:var(--ink)}
.section--blue .btn--primary:hover{background:#e7eae9;color:var(--ink)}
.section--blue .btn--ghost{border-color:rgba(255,255,255,.38);color:#fff}
.section--blue .btn--ghost:hover{border-color:#fff;color:#fff}
.section--blue .link-arrow{border-color:rgba(255,255,255,.38)}
.section--blue .link-arrow:hover{border-color:#fff;color:#fff}

/* HÄR LÅG EN GUL RADIAL i heron, .hero::after med rgba(255,192,0,.17) i en
   ellips nere till vänster, tänkt att knyta märkesfärgen till fotot. Borttagen
   på Julius besked.

   Den satt kvar från när skrimmet var mörkt över hela bilden och gulnaden
   lästes som ljus i en skugga. Sedan högersidan öppnats till 9 procents
   täckning är fotot ett foto igen, och då lade sig radialen som en färgslöja
   över gräset i stället: en varm ton över något som redan har en egen.

   Skiktordningen står kvar. Radialen låg på z-index 1, mellan bilden och
   texten, och de två raderna nedan är det som faktiskt håller ordningen. Ta
   inte bort dem för att regeln över dem är borta. */
.hero__media{z-index:0}
.hero__body{z-index:2}

/* --- Intent router (directly under the hero) ------------------------------
   Four self-identifying routes so a visitor with a specific need lands on the
   right page immediately instead of reading the whole funnel to find it. */
/* Bandet bär sin egen botten: LOGOTYPBLÅ --brand, #024D68, inte den djupa
   --brand-900 som de andra två mosaikbanden ligger på. Det är den färgen som
   gör bandet vänligt, och skälet till att avsiktsväxeln inte läses som ännu
   ett mörkt band mitt på sidan. section--blue är struken ur markupen, annars
   hade två bottnar legat över varandra. */
.intent-band{background-color:var(--brand);color:#fff}
/* ÖVERGÅNGEN FRÅN HJÄLTEN. Bandet ligger direkt under hjältens foto, som slutar
   mörkt, och mot bandets ljusare logotypblå blev skarven en rak linje tvärs över
   sidan. Tonen lägger den djupa --brand-900 överst och släpper den till
   ingenting: överkanten möter fotot i dess egen mörka ände och bandet är sin
   egen färg långt innan rubriken börjar.

   Stoppen är inte jämnt fördelade. En rak toning tar slut på ett bestämt ställe
   och den kanten syns nästan lika tydligt som den skarv den skulle dölja, så
   fallet är brantast överst och plattar ut mot noll.

   Höjden är sektionens egen och inte rutnätets, samma skäl som gäller luften i
   __inner nedan: den ska sluta ovanför rubriken på varje bredd, inte falla på en
   fog. Den ligger över mosaiken och slöjan (z-index 1) men före __inner i
   källordningen, så innehållet står kvar överst. */
.intent-band::before{
  content:"";position:absolute;left:0;right:0;top:0;
  height:clamp(88px,9vw,132px);
  z-index:1;pointer-events:none;
  background:linear-gradient(180deg,
    rgba(1,44,60,.55) 0%, rgba(1,44,60,.38) 22%,
    rgba(1,44,60,.20) 48%, rgba(1,44,60,.07) 74%,
    rgba(1,44,60,0) 100%);
}
/* Slöjan är i bandets EGEN blå, inte i den djupa: den ska dra mosaiken mot
   #024D68 och inte mot något mörkare, annars tappar sektionen sin ton. Den
   tonar nedåt, för rubriken står högst upp och behöver täckning medan korten
   under är ljusa av sig själva. */
.intent-band__veil{
  background:linear-gradient(172deg,
    rgba(2,77,104,.86) 0%, rgba(2,77,104,.66) 28%,
    rgba(2,77,104,.50) 60%, rgba(2,77,104,.46) 100%);
}
/* Luften är sektionens egen, inte rutnätets: --pad-md upptill och 0,78 av den
   nertill. Ett tidigare försök satte allt i hela plattor och blev tre gånger
   så luftigt som sektionen det ersätter. Sidindraget och den nedre
   vadderingen sätts av js/tiles.js, för det är de som måste ligga i fog. */
.intent-band__inner{
  position:relative;z-index:1;
  padding-top:var(--pad-md);
  padding-bottom:calc(var(--pad-md) * .78);
  padding-inline:var(--gutter);
}
.intent-band__head{
  display:flex;flex-wrap:wrap;align-items:end;justify-content:space-between;
  gap:16px 40px;margin-bottom:32px;
}
/* Kolumner, kortbredd och mellanrum sätts av js/tiles.js ur data-tile-cols.
   Reservvärdena gäller tills första mätningen. */
.intent-grid{
  display:grid;
  grid-template-columns:repeat(var(--cols,4),var(--card-w,minmax(0,1fr)));
  /* Rad först, kolumn sedan. De skiljer sig i EN kolumn, där kolumnavståndet
     är noll men korten fortfarande ska ha luft mellan sig. Se tiles.js. */
  gap:var(--card-row-gap,16px) var(--card-gap,16px);
}
.intent{
  position:relative;display:flex;flex-direction:column;gap:6px;
  height:var(--card-h,auto);
  border-radius:var(--tile-r,var(--radius));text-decoration:none;color:#fff;
  /* En LJUS panel ovanpå mosaiken, inte en mörk urgröpning. Den lägger sig som
     en ljusning i stället för att gräva ur bandet, vilket är hela skillnaden
     mellan vänligt och tungt. */
  background-color:rgba(255,255,255,.09);
  /* SAMMA LJUS SOM DEN GULA RUTAN, bara från den kalla sidan. Noten vid
     .intent--call längre ned har alltid sagt att bärnstenen är "lit from the
     same corner as the blue panels" — men de blå hade ingen belysning alls,
     bara en platt genomskinlig fyllning med en tunn kant på ovansidan. Det var
     den gula som bar hela djupet och de tre blå som såg klistrade ut bredvid.
     Geometrin här är ordagrant densamma som gulets: samma radial från 84% -12%
     och samma 152-gradersdiagonal, så alla fyra korten är tända från samma
     hörn och skillnaden mellan dem är färgen och ingenting annat.
     Håll värdena låga. Glaset ska fortfarande läsa som glas mot mosaiken; det
     här är en ljusning i ytan, inte en fyllning ovanpå den. */
  background-image:
    radial-gradient(92% 132% at 84% -12%,rgba(255,255,255,.20) 0%,rgba(255,255,255,0) 62%),
    linear-gradient(152deg,rgba(255,255,255,.15) 0%,rgba(255,255,255,0) 46%,rgba(1,32,44,.15) 100%);
  border:1px solid rgba(255,255,255,.24);
  /* Frostat glas. Mosaiken går suddig innanför kortet och skarp utanför, så
     kanten blir tydlig av sig själv och oskärpan förstärker uppställningen i
     stället för att dölja den. Oskärpan är satt mot PLATTAN, inte i fasta
     pixlar: 10 px mot den gamla 24-pixelsplattan lät mönstret överleva
     innanför glaset, men mot dagens 15,6 raderade samma tal det helt, och
     korten läste som ett främmande material ovanpå mosaiken i stället för
     som glas på den. 6 px är samma andel av plattan som 10 var av 24. */
  backdrop-filter:blur(6px) saturate(1.3);
  -webkit-backdrop-filter:blur(6px) saturate(1.3);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.26),
    0 12px 26px -20px rgba(1,20,28,.75);
  transition:background-color .25s ease,border-color .25s ease,transform .3s var(--ease-out);
}
/* background-color och inte background: kortformen hade nollställt
   background-image och släckt ljuset i samma sekund som pekaren kom in. */
.intent:hover{
  background-color:rgba(255,255,255,.16);border-color:rgba(255,255,255,.46);
  transform:translateY(-3px);color:#fff;
}
/* Utan backdrop-filter blir kortet bara halvgenomskinligt och texten står
   direkt på den ljusaste driften. Då får det en tätare fyllning i stället. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  /* background-color, av samma skäl som i hover-regeln: kortformen hade tagit
     ljuset med sig och lämnat en platt ruta i just de webbläsare som redan
     saknar oskärpan. */
  .intent{background-color:rgba(255,255,255,.16)}
}
.intent__label{font-size:18px;font-weight:600;letter-spacing:-.01em;line-height:1.28;padding-right:28px}
.intent__sub{font-size:15px;color:rgba(255,255,255,.72);text-wrap:pretty}
/* Pilen flyttar upp i hörnet. Nere till vänster krävde den 52 px botten-
   vaddering i kortet, alltså ett mått som inte är en platta, och kortets luft
   är en hel platta på alla fyra sidor nu. */
/* Pilen står i samma indrag som texten, 1,5 plattor. Den låg på 1 platta och
   hamnade synligt närmare hörnet än radens början när vadderingen växte. */
.intent__arrow{
  position:absolute;top:calc(var(--t,16px) * 1.5);right:calc(var(--t,16px) * 1.5);
  width:18px;height:18px;
  color:var(--accent);transition:transform .3s var(--ease-out);
}
.intent:hover .intent__arrow{transform:translate(4px,-4px)}
/* The amber tile is lit from the same corner as the blue panels, just from the
   warm side: sunlit top-right falling to the shaded amber at the bottom-left.
   Flat #FFC000 read as a sticker next to the translucent tiles. */
.intent--call{
  background-color:var(--accent);
  background-image:
    radial-gradient(92% 132% at 84% -12%,var(--accent-lit) 0%,rgba(255,215,94,0) 62%),
    linear-gradient(152deg,rgba(255,255,255,.26) 0%,rgba(255,255,255,0) 46%,rgba(2,44,60,.14) 100%);
  border-color:var(--accent-shade);color:var(--ink);
  /* Ingen backdrop bakom en täckande yta, och ingen glaskant över gulet. */
  backdrop-filter:none;-webkit-backdrop-filter:none;
  box-shadow:0 12px 26px -20px rgba(1,20,28,.75);
}
.intent--call:hover{
  background-color:var(--accent-hover);
  border-color:#D89800;color:var(--ink);
}
.intent--call .intent__sub{color:rgba(18,25,29,.72)}
.intent--call .intent__arrow{color:var(--ink)}

/* Brytpunkterna ligger i data-tile-cols på elementet, inte här: antalet
   kolumner måste vara känt av skriptet för att bredderna ska gå jämnt upp i
   plattor, och två sanningar om samma sak hade glidit isär. */

/* ==========================================================================
   Kalkyl — battery yield calculator
   Lives on lonsamhet.html, under the räkneexempel. Pick a house size and
   whether an EV charges at home; nothing is typed in, so there are no input
   styles here — both columns of .calc-grid are read-only .rrow lists.
   ========================================================================== */
.calc-card{
  background:var(--surface-raised);border:1px solid var(--hair);
  border-radius:var(--radius);box-shadow:var(--shadow-sm);
  padding:clamp(20px,2.6vw,36px);
}

/* house / EV picker */
/* Tre spår för tre kort. Stod på fyra så länge elbilsknappen räknades
   med i raden; när den flyttade ut till .evrow blev det fjärde spåret en
   tom kolumn som klämde ihop de tre korten. Knappen finns inte längre. */
.calc-picker{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}
.hcard{
  position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:4px;
  padding:16px;padding-top:96px;border:1px solid var(--hair);border-radius:var(--radius);
  background:var(--surface-raised);text-align:left;cursor:pointer;
  transition:border-color .25s ease,box-shadow .25s ease,transform .25s var(--ease-out);
}
.hcard:hover{border-color:var(--ink-faint);transform:translateY(-2px)}
.hcard[aria-pressed="true"]{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent),var(--shadow-md)}
.hcard__img{
  position:absolute;left:50%;top:6px;transform:translateX(-50%);
  width:78%;height:84px;object-fit:contain;pointer-events:none;
}
.hcard__check{
  display:block;width:19px;height:19px;margin-bottom:8px;border-radius:4px;
  border:1px solid var(--hair);background:var(--surface);
  transition:background-color .2s ease,border-color .2s ease;
}
.hcard[aria-pressed="true"] .hcard__check{background:var(--accent);border-color:var(--accent)}
.hcard__name{font-size:16px;font-weight:600;letter-spacing:-.01em}
.hcard__sub{font-size:14px;color:var(--ink-muted)}
.hcard--ev{padding-top:16px;justify-content:center}
.hcard--ev .hcard__name{font-size:14px;color:var(--ink-muted);font-weight:500}
.hcard--ev .hcard__sub{font-size:16px;font-weight:600;color:var(--ink)}
@media (max-width:900px){
  .calc-picker{grid-template-columns:repeat(2,minmax(0,1fr))}
  .hcard--ev{padding-top:16px}
}
@media (max-width:520px){.calc-picker{grid-template-columns:minmax(0,1fr)}}

.calc-rule{height:1px;background:var(--hair);border:0;margin:32px 0}

.calc-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:56px}
@media (max-width:860px){.calc-grid{grid-template-columns:minmax(0,1fr);gap:36px}}
.calc-col__title{margin-bottom:14px;font-size:15px;font-weight:500;color:var(--ink-faint)}
.calc-note{margin-top:16px;font-size:14px;line-height:1.6;color:var(--ink-faint)}

.rrow{display:flex;align-items:baseline;justify-content:space-between;gap:20px;
  padding:14px 0;border-bottom:1px solid var(--hair)}
.rrow span:first-child{font-size:16px}

/* Årsförbrukningen i kortets högra hörn. Absolut placerad så den inte trycker
   ned bilden eller namnet — kortet har redan en fast bildhöjd att hålla. */
.hcard__kwh{
  position:absolute;top:12px;right:12px;z-index:1;
  padding:3px 9px;border-radius:999px;
  background:var(--surface);border:1px solid var(--hair);
  font-family:var(--fig);font-size:12px;line-height:1.5;color:var(--ink-muted);
  font-variant-numeric:tabular-nums;
}
.hcard[aria-pressed="true"] .hcard__kwh{border-color:var(--accent);color:var(--ink)}

/* Den lilla husväljaren inne i uppdelningen. Samma val som korten högst upp,
   annan storlek: här är huset en inställning till en tabell, inte sidans
   fråga. Vågrät rad, bild till vänster om texten, så tre kort får plats på
   en telefonbredd utan att bli tre skärmhöjder. */
.hswitch{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:20px}
.hmini{
  display:flex;align-items:center;gap:10px;padding:8px 12px 8px 8px;
  border:1px solid var(--hair);border-radius:var(--radius);
  background:var(--surface-raised);text-align:left;cursor:pointer;
  transition:border-color .2s ease,box-shadow .2s ease;
}
.hmini:hover{border-color:var(--ink-faint)}
.hmini:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
.hmini[aria-pressed="true"]{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}
.hmini__img{flex:none;width:52px;height:32px;object-fit:contain}
.hmini__name{font-size:15px;font-weight:600;letter-spacing:-.01em}
.hmini__meta{
  display:block;width:100%;
  font-family:var(--fig);font-size:11.5px;line-height:1.45;color:var(--ink-faint);
}
/* Namn och metarad på samma sida om bilden, staplade. */
.hmini{flex-wrap:wrap}
.hmini__name,.hmini__meta{flex:1 1 auto;min-width:0}
.hmini__meta{margin-left:62px;margin-top:-2px}

/* Fallets förutsättningar, en rad ovanför tabellen. */
.facts{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;
  margin-bottom:24px;padding-bottom:20px;border-bottom:1px solid var(--hair);
}
.facts__i dt{font-size:13px;color:var(--ink-faint);margin-bottom:2px}
.facts__i dd{font-family:var(--fig);font-size:17px;font-variant-numeric:tabular-nums}

/* Radnyckeln bär numera en infoknapp, så den är inte längre bara text. */
/* VÄRDET ÄR INTE LÄNGRE SISTA BARNET. Högerkolumnens rader har tre delar —
   nyckel, värde, skillnad — så `span:last-child` träffade skillnaden och
   värdet tappade sitt nowrap. Följden syntes bara på telefon: "9 405 kWh"
   bröts till "9 405" och "kWh" på var sin rad mitt i en sifferkolumn.
   Regeln pekar på allt som INTE är nyckeln i stället, vilket täcker båda
   kolumnerna oavsett hur många delar raden har. */
.rrow__k{display:inline-flex;align-items:center;gap:7px;font-size:16px;flex:1 1 auto}
.rrow > span:not(.rrow__k){
  font-family:var(--fig);font-size:17px;font-variant-numeric:tabular-nums;white-space:nowrap;
}

/* Skillnaden mot kolumnen bredvid, satt liten och grön intill talet. Den står
   bara i högerkolumnen: det är där batteriet gör något. */
.rrow__d{
  font-family:var(--fig);font-size:13px;font-variant-numeric:tabular-nums;
  /* Nedtonat på kommentar 5. Plusvärdet är en bikommentar till talet bredvid,
     inte ett eget resultat, och i full grön styrka drog det blicken före den
     siffra det kommenterar. Samma kulör, lägre mättnad och ljushet. */
  color:#5c8f74;white-space:nowrap;flex:none;
}

/* Infoknappen. Rund, låg kontrast tills man pekar — den ska finnas på varje
   rad utan att tolv stycken ser ut som tolv varningar. */
.info{
  flex:none;width:18px;height:18px;padding:0;border-radius:999px;
  border:1px solid var(--hair);background:var(--surface);
  color:var(--ink-faint);font-family:var(--fig);font-size:11px;line-height:1;
  cursor:pointer;transition:border-color .15s ease,color .15s ease,background-color .15s ease;
}
.info:hover{border-color:var(--ink-faint);color:var(--ink)}
.info:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
.info[aria-expanded="true"]{background:var(--ink);border-color:var(--ink);color:var(--surface)}
.infobox{
  margin:-6px 0 0;padding:12px 14px;border-radius:var(--radius);
  background:var(--surface);border:1px solid var(--hair);
  font-size:14px;line-height:1.6;color:var(--ink-muted);
}

/* ==========================================================================
   Bokningssidans topp — .bookhero
   Fristående block. Tvådelning efter Julius tre förlagor: en visuell halva och
   en halva med formuläret. Ljus, inte mörk som förlagorna.
   ========================================================================== */
.booksplit{display:grid;gap:clamp(32px,4vw,64px)}
@media (min-width:981px){
  /* Formuläret får den bredare halvan. Det är sidans uppgift; texten till
     vänster är dess motivering och behöver mindre plats. */
  .booksplit{grid-template-columns:minmax(0,0.92fr) minmax(0,1fr);align-items:start}
}

/* Chippen. Lånade från förlagan, men de bär ett svar och inte en egenskap:
   var och en tar bort en invändning mot att boka. Låg kontrast med flit — de
   ska läsas som upplysningar, inte som fyra utrop. */
.bookchips{list-style:none;display:flex;flex-wrap:wrap;gap:8px;margin:0}
.bookchips li{
  padding:7px 14px;border:1px solid var(--hair);border-radius:999px;
  background:var(--surface-raised);
  font-size:14px;line-height:1.3;color:var(--ink-muted);
}

/* Fotot i egen ram i stället för i full bredd med slöja över. Det var det
   tyngsta på sidan och gjorde formuläret till något som låg ovanpå en bild. */
.bookshot{margin:0}
.bookshot img{
  display:block;width:100%;height:auto;border-radius:var(--radius);
  border:1px solid var(--hair);
}
@media (min-width:981px){
  /* Bilden är 16:9 i filen. I den proportionen slutar vänsterspalten ungefär
     260 px under formuläret, alltså en tom yta bredvid en färdig ruta. Beskuren
     till 2:1 landar spalterna på ungefär samma höjd i steg 1. Exakt kan det
     inte bli — rutan växer i steg 2 — men skillnaden går från ett hål till
     luft. object-fit:cover, alltså beskuren och aldrig deformerad. */
  .bookshot img{aspect-ratio:2/1;object-fit:cover}
}
/* Egen regel, i alla bredder. På kontakt står fotot FÖRE uppgifterna på telefon,
   och i 16:9 sköt det ner telefonnumret under vikningen. 2:1 håller det uppe. */
.bookshot--flat img{aspect-ratio:2/1;object-fit:cover}
.bookshot figcaption{
  margin-top:10px;font-size:13.5px;line-height:1.6;color:var(--ink-faint);
}

/* Formuläret står på en egen yta med en hårfin ram, så att fälten hör ihop
   utan att rutan skriker. Ingen skugga: en upphöjd låda hade gjort samma sak
   som slöjan gjorde, alltså tagit uppmärksamhet från innehållet. */
.booksplit__form{
  padding:clamp(20px,2.4vw,32px);
  background:var(--surface-raised);
  border:1px solid var(--hair);border-radius:var(--radius);
}
.booknote{
  margin-top:16px;font-size:13.5px;line-height:1.7;color:var(--ink-faint);
}
.booknote a{color:var(--ink-muted)}
.booknote span[aria-hidden]{margin:0 6px;opacity:.5}
  /* På kontakt.html är högerspalten inte bara kortet: kortet och raden under
     det delar en spalt. Utan omslaget hade raden blivit ett tredje rutobjekt
     och hamnat under VÄNSTERSPALTEN i rad två. */
  .booksplit__col{min-width:0}
  .booknote--under{padding:0 2px;margin-top:14px}

@media (max-width:980px){
  /* På telefon kommer formuläret först. Den som klickat "Boka" har redan
     bestämt sig; motiveringen ska inte stå i vägen för fälten. */
  .booksplit__form{order:-1}
  /* MEN INTE på kontakt.html, som också bygger på .booksplit. Att kasta om
     spalterna var rätt på boka.html: den som klickat "Boka" har bestämt sig,
     och motiveringen ska inte stå i vägen för fälten. På kontakt landar man
     utan att ha valt något, och en naken ruta ovanför sidans rubrik ser ut som
     ett fel. Därför naturlig ordning där — rubrik, ingress, foto, uppgifter —
     och .booksplit__col får ingen order alls. */
  .booksplit__side .bookshot{margin-top:24px}
}

/* ==========================================================================
   Produktsidan: mindre text i märkeskorten, och en punktlista
   Fristående block.
   ========================================================================== */

/* Märkeskortens beskrivningar låg på brödtextgrad i en 309 px spalt och blev
   fyra rader var, tolv kort i rad. Julius kommentar: mindre text på allihop.
   Texterna är kortade i markup; det här sänker graden också, så kortet läser
   som en bildtext till produktbilden ovanför i stället för som ett stycke. */
.brand__body p{font-size:14.5px;line-height:1.55}

/* .pointlist — punkter i stället för prosablock.
   Flera av produktsidans avsnitt var tre stycken löptext där innehållet i
   själva verket var tre påståenden. Listan gör dem läsbara utan att göra dem
   till en punktlista av enstaka ord: varje rad har en fet ingång och en
   förklaring efter, alltså fortfarande meningar. */
.pointlist{list-style:none;display:grid;gap:10px;margin:0}
.pointlist li{
  position:relative;padding-left:20px;
  font-size:15px;line-height:1.6;color:var(--ink-muted);
}
.pointlist li::before{
  content:"";position:absolute;left:0;top:.62em;
  width:6px;height:6px;border-radius:999px;background:var(--accent);
}
.pointlist b{color:var(--ink);font-weight:600}

/* .panelshot — produktbilden i solpanelsfliken. Smal med flit: en panel i en
   ruta ska illustrera raderna under, inte ta halva skärmen. Fristående block. */
/* BILDTEXTEN STÅR BREDVID BILDEN, inte under den. Figuren var 300 px bred i en
   rad på 1124, alltså 824 px tomt bredvid en ensam produktbild, och det läste
   som en rutnätscell vars grannar saknades: en plats där ett foto till skulle
   in. Det kommer inga fler foton, så hålet skulle inte fyllas av sig självt.

   Bilden och texten bildar nu ETT objekt i stället för en cell med en fotnot.
   Bredden är beskuren till dryga 700 px så att figuren läser som en figur och
   inte som en spalt: en 300 px bild plus en bildtext som spänner resten av
   raden hade gett två meningar på 790 px, alltså en rad text i en tom remsa. */
/* PANELEN SOM FIGUR. Filen är 373x752, alltså en stående form dubbelt så hög
   som bred, och bildtexten är tre rader. Ställda sida vid sida i sin ursprung-
   liga skala gick de aldrig ihop: panelen tornade, texten hamnade strandad på
   halva höjden och under den låg ett tomrum. Det var felet Julius såg.
   Greppet nu är ett LIGGANDE band över hela spalten. Panelen är nedskalad tills
   proportionen slutar dominera, står i en egen ruta till vänster med en hårlinje
   mot texten, och bildtexten fyller resten på höjden den behöver. Bandet har en
   ram: filen har vit inbränd botten och ingen alfakanal, så utan ram svävar
   panelen mot sidans egen nästan vita botten — samma skäl som i noten vid
   markupen. Ingen bakgrundston, den skulle rita en vit fyrkant mitt i tonen. */
.panelshot{
  /* Bredden kapad till vad bildtexten faktiskt mäter. Över hela spalten blev
     textkolumnen bredare än de 64ch texten får bli, och då stod det tomt till
     höger om raderna inne i själva ramen — vilket ser trasigt ut på ett sätt
     tomrum UTANFÖR en ram inte gör. 236 för bilden plus textens eget mått. */
  max-width:900px;margin:0;
  display:grid;grid-template-columns:minmax(0,1fr);
  background-color:var(--surface-raised);
  border:1px solid var(--hair);border-radius:var(--radius);
  overflow:hidden;
}
.panelshot .media-slot{
  max-width:none;width:100%;height:210px;
  display:grid;place-items:center;
  border-bottom:1px solid var(--hair);border-radius:0;
  background-color:var(--surface-raised);
}
/* Höjden i pixlar och inte i procent: en procentuell höjd mot en rutnätscell
   utan bestämd höjd löser upp sig till none, vilket är samma fälla som i
   .shopcard__media img. 170 av rutans 210 lämnar luft över och under. */
.panelshot .media-slot img{
  width:100%;height:170px;object-fit:contain;object-position:center;
  max-width:none;max-height:none;
}
.panelshot figcaption{
  margin:0;padding:18px 22px 20px;
  max-width:64ch;
  font-size:15px;line-height:1.65;color:var(--ink-muted);text-wrap:pretty;
}
@media (min-width:760px){
  .panelshot{grid-template-columns:236px minmax(0,1fr);align-items:center}
  .panelshot .media-slot{
    height:100%;min-height:212px;
    border-bottom:0;border-right:1px solid var(--hair);
  }
  .panelshot figcaption{padding:22px 28px}
}
/* Panelen är stående, 373x752, alltså nästan dubbelt så hög som bred. I
   .media-slot:s vanliga ruta — 230 px hög — blev den 80 px bred och läste som
   en miniatyr. Rutan är därför högre här.
   --trim är produktens andel av bildens höjd och står i formeln vid
   .media-slot--photo img. Mätt på filen: bläcket är 696 px av 752, alltså
   0,925. Byts bilden ut ska trimmen mätas om, annars skalas den fel — det står
   utskrivet i noten där. */
.panelshot .media-slot img{--trim:.925}
.panelshot figcaption{
  margin-top:12px;font-size:15px;line-height:1.65;color:var(--ink-muted);text-wrap:pretty;
}

/* ==========================================================================
   .lpmedia — kartan och fotot sida vid sida på landningssidorna
   Fristående block.
   ========================================================================== */
.lpmedia{display:grid;gap:clamp(20px,3vw,40px)}
@media (min-width:860px){
  /* Kartan är kvadratisk, fotot 16:9. Med align-items:start blev fotot 170 px
     kortare än kartan och lämnade ett hål under sig i högerspalten. Raden får
     i stället sin höjd av kartan, och fotot sträcks till samma höjd med
     object-fit:cover — beskuret i sidled, aldrig deformerat. Motivet ligger
     mitt i bilden, så beskärningen tar bort häck och gräs, inte huset. */
  .lpmedia{grid-template-columns:minmax(0,0.86fr) minmax(0,1fr);align-items:start}
  /* RÖR INTE .map-embed. Den håller sin kvadrat med aspect-ratio, och ett
     flex:1 på den vann över kvadraten: kartan krympte från 499 till 300 px och
     blev en liten ruta i en stor spalt. Fotot får i stället en proportion som
     möter kartans höjd — spalterna är 0,86 mot 1, kartan är kvadratisk, alltså
     ska fotot vara 1/0,86 ≈ 1,16 brett mot högt. object-fit:cover beskär i
     sidled, aldrig deformerar; motivet ligger mitt i bilden. */
  .lpmedia__img{aspect-ratio:1.16;object-fit:cover}
}
.lpmedia__fig{margin:0}
.lpmedia__img{
  display:block;width:100%;height:auto;border-radius:var(--radius);
  border:1px solid var(--hair);
}
.lpmedia figcaption{
  margin-top:12px;font-size:13.5px;line-height:1.6;color:var(--ink-faint);
}
.lpmedia figcaption a{color:var(--ink-muted)}

/* ==========================================================================
   .deep — det som får finnas men inte synas
   Fristående block.

   Antagandelistan, metoden och referenshuset låg utskrivna under batterisiffran
   och var fyra skärmar text efter det tal sidan finns för att visa. De är
   hopfällda i stället för borttagna: sex sidor länkar hit och lovar uttryckligen
   att antagandena står utskrivna, och varfor-oss.html länkar till
   #referenshuset. Raderat hade sex sidor börjat ljuga.

   Ser ut som en rad, inte som ett kort. Den som vill ha den öppnar den; den som
   inte vill ska knappt märka att den finns.
   ========================================================================== */
.deep{margin-top:56px;border-top:1px solid var(--hair)}
.deep>summary{
  display:flex;align-items:center;gap:10px;
  padding:20px 0;cursor:pointer;list-style:none;
  font-size:16px;font-weight:500;color:var(--ink-muted);
  transition:color .2s ease;
}
.deep>summary::-webkit-details-marker{display:none}
.deep>summary:hover{color:var(--ink)}
.deep>summary:focus-visible{outline:2px solid var(--blue);outline-offset:3px}
/* Egen pil, för den ska peka nedåt när rutan är stängd och uppåt när den är
   öppen. Ritad i css i stället för som ikon: en triangel är inte värd ett
   svg-symbol och en <use> till. */
.deep>summary::after{
  content:"";width:8px;height:8px;flex:none;
  border-right:1.6px solid currentColor;border-bottom:1.6px solid currentColor;
  transform:translateY(-2px) rotate(45deg);transition:transform .2s var(--ease-out);
}
.deep[open]>summary::after{transform:translateY(2px) rotate(-135deg)}
.deep[open]>summary{color:var(--ink)}
.deep__body{padding-bottom:8px}
/* Blocken inuti bar egna toppmarginaler när de stod utskrivna. Inuti rutan
   sitter de redan under en rubrik, så den första ska inte ha någon. */
.deep__body>*:first-child{margin-top:0;padding-top:0;border-top:0}

/* ==========================================================================
   .factlist — etikett plus en mening
   Fristående block.

   Finns för att .kv INTE duger till det här. .kv är sajtens nyckel/värde-par
   och dess värdeslot är satt i monofonten med white-space:nowrap, för den är
   byggd för SIFFROR. Stoppar man in en mening i den bryts den inte: på
   landningssidan sköt tre <dd> dokumentet till 1118 px vid en 390 px vy och
   hela sidan gick att dra i sidled.

   Skillnaden mot .kv i sak: etikett över text i stället för bredvid, brödtext
   i stället för mono, och radbrytning tillåten. Använd .kv till tal och den
   här till meningar.
   ========================================================================== */
.factlist{margin:0;border-top:1px solid var(--hair)}
.factlist>div{padding:16px 0;border-bottom:1px solid var(--hair)}
.factlist dt{font-size:16px;font-weight:600;letter-spacing:-.01em;margin-bottom:4px}
.factlist dd{margin:0;font-size:15px;line-height:1.65;color:var(--ink-muted)}
.factlist dd b{color:var(--ink);font-weight:600}

@media (min-width:760px){
  /* Två spalter när det finns plats. Etiketten är kort och texten är kort;
     en spalt ger fyra rader luft till höger om varje mening. */
  .factlist{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
    column-gap:clamp(24px,3vw,56px);border-top:0}
  .factlist>div{border-top:1px solid var(--hair);border-bottom:0}
}

/* ==========================================================================
   Så beräknas resultatet — metodavsnittet på lonsamhet.html
   Fristående regelblock. Klämmer man in en regel i en selektorlista gäller den
   hela listan; det hände en gång i huvudet och kostade en halv dag.
   ========================================================================== */
.method{
  margin-top:32px;padding-top:32px;border-top:1px solid var(--hair);
}
.method__head{
  display:grid;grid-template-columns:minmax(0,22ch) minmax(0,1fr);
  gap:clamp(20px,3vw,48px);align-items:baseline;margin-bottom:28px;
}
.method__title{font-size:clamp(20px,2vw,24px);font-weight:600;letter-spacing:-.01em}
.method__intro{font-size:16px;line-height:1.6;color:var(--ink-muted)}

/* Numren är stegets ordning och bär betydelse, så de kommer från listan och
   skrivs inte in. counter-reset på ol, counter-increment på li: en punkt som
   flyttas eller tas bort numrerar om sig själv. */
.method__steps{list-style:none;counter-reset:steg;display:grid;gap:20px}
.method__steps>li{
  counter-increment:steg;position:relative;
  padding:18px 20px 18px 62px;
  border:1px solid var(--hair);border-radius:var(--radius);
  background:var(--surface);
}
.method__steps>li::before{
  content:counter(steg);
  position:absolute;left:20px;top:18px;
  width:26px;height:26px;border-radius:999px;
  background:var(--brand);color:#fff;
  font-family:var(--fig);font-size:13px;line-height:26px;text-align:center;
  font-variant-numeric:tabular-nums;
}
.method__steps h4{font-size:16px;font-weight:600;margin-bottom:6px;letter-spacing:-.01em}
.method__steps p{font-size:15px;line-height:1.65;color:var(--ink-muted)}
.method__steps p+p{margin-top:8px}
.method__steps b{color:var(--ink);font-weight:600}

/* Brasklappen. Den ska gå att läsa som en brasklapp och inte som ännu ett steg,
   därför ingen ruta och ingen siffra — men fetstilen först, så den mening som
   juridiskt betyder mest inte försvinner i en grå klump. */
.method__caveat{
  margin-top:20px;font-size:14px;line-height:1.65;color:var(--ink-faint);
}
.method__caveat b{color:var(--ink-muted);font-weight:600}

@media (max-width:760px){
  .method__head{grid-template-columns:1fr;gap:10px}
  .method__steps>li{padding:16px 16px 16px 54px}
  .method__steps>li::before{left:16px;top:16px}
}

/* Kolumnens netto. Tyngre än en .rrow, för det är radernas summa. */
.rsum{
  display:flex;align-items:baseline;justify-content:space-between;gap:20px;
  margin-top:4px;padding:16px 0 0;border-top:2px solid var(--ink);
  font-size:16px;font-weight:600;
}
.rsum span:last-child{font-family:var(--fig);font-size:19px;font-variant-numeric:tabular-nums}
.rsum--good span:last-child{color:var(--green,#1c7c4a)}

.calc-col__title small{display:block;margin-top:2px;font-size:13px;font-weight:400;color:var(--ink-faint)}
.calc-col--med{position:relative}

/* Totalen ligger under båda kolumnerna, för den är skillnaden mellan dem. */
.total--wide{margin-top:28px}

@media (max-width:760px){
  /* Tre delar får inte plats på en rad vid 390 px. Skillnaden lägger sig på
     egen rad under värdet, högerställd, i stället för att klämma sönder det. */
  .rrow{flex-wrap:wrap}
  .rrow__d{width:100%;text-align:right;margin-top:3px}
  .hswitch{grid-template-columns:1fr;gap:8px}
  .hmini__meta{margin-left:0}
  .facts{grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
}

.total{margin-top:24px;padding:24px;background:var(--surface);border-radius:var(--radius)}
.total__label{font-size:15px;font-weight:500;color:var(--ink-muted)}
.total__fig{
  display:flex;align-items:baseline;gap:8px;margin:6px 0 10px;
  font-family:var(--fig);font-weight:500;line-height:1;letter-spacing:-.03em;
  font-size:clamp(44px,5.4vw,64px);color:var(--blue);font-variant-numeric:tabular-nums;
}
.total__fig small{font-family:var(--sans);font-size:19px;font-weight:400;color:var(--ink-muted);letter-spacing:0}
/* Named, not `.total p` — a bare element selector here scores higher than
   .total__fig and silently rendered the headline number at 15px grey instead
   of 64px blue. Keep every child of .total addressed by its own class. */
.total__note{font-size:15px;line-height:1.6;color:var(--ink-muted);text-wrap:pretty}

.calc-assump{margin-top:28px;padding-top:20px;border-top:1px solid var(--hair);
  font-size:14px;line-height:1.7;color:var(--ink-faint);text-wrap:pretty}

/* ==========================================================================
   Section grounds & page rhythm
   Six grounds, three light and three dark, each a clear step from the last:

     paper   #FFFFFF      .section--paper / .section--white
     surface #F1F5F7      (page default — no class)
     tint    --brand-50   .section--tint
     ─────────────────────────────────────────
     blue    --brand      .section--blue   saturated feature block
     dark    --brand-800  .section--dark   proof / detail block
     deep    --brand-900  .section--deep   page close, once per page

   A page never repeats a ground back to back, and never places two darks in
   sequence. Amber stays the 10%: the closing CTA and small active states.
   ========================================================================== */
.section--paper{background-color:var(--surface-raised)}
.section--tint{background-color:var(--brand-50)}
.section--tint .tile,.section--tint .card{border-color:var(--brand-100)}
.section--tint .card{background:var(--surface-raised)}
.section--tint .card--flat{background:var(--brand-25)}
.section--tint .section--top,.section--tint+.section--top{border-color:var(--brand-100)}

/* deep blue punctuation — replaces the old near-black bands */
.section--deep{background-color:var(--brand-900);color:var(--dark-fg)}
/* Samma ögonbrynsfärg som varje annan mörk grund: vitt på 72 %. */
.section--deep .eyebrow{color:rgba(255,255,255,.72)}
.section--deep p:not(.note){color:var(--dark-body)}
.section--deep .note{color:#8FB6C6}
.section--deep .btn--ghost-dark{border-color:rgba(234,243,247,.34);color:var(--dark-fg)}
.section--deep .btn--ghost-dark:hover{border-color:var(--dark-fg)}

/* ── Amber accent band — the 10% ───────────────────────────────────────────
   The blue panels are a cool ground with warm light falling across them. This
   is the same physics, flipped: a warm ground, the sun landing top-right, and
   the far corner dropping into a brand-blue shadow — so the band reads as a
   lit surface and still belongs to the palette instead of floating off it as a
   slab of #FFC000. Three layers, painted back to front:
     1  the ramp: lit amber → logo amber → shaded amber, raked across the band
     2  the sun: a bloom off the top-right corner
     3  the shadow: brand blue pooling in the bottom-left
   ───────────────────────────────────────────────────────────────────────── */
.section--accent{
  position:relative;color:var(--ink);
  background-color:var(--accent);
  background-image:
    radial-gradient(74% 118% at 84% -14%,var(--accent-lit) 0%,rgba(255,215,94,0) 58%),
    radial-gradient(88% 112% at 2% 114%,rgba(1,44,60,.30) 0%,rgba(1,44,60,0) 62%),
    linear-gradient(154deg,#FFCB3F 0%,var(--accent) 44%,var(--accent-shade) 100%);
}
.section--accent .eyebrow{color:rgba(14,26,32,.66)}
.section--accent p{color:rgba(14,26,32,.8)}
.section--accent .btn--primary{background:var(--brand);color:#fff}
.section--accent .btn--primary:hover{background:var(--brand-900);color:#fff}
.section--accent .btn--ghost,.section--accent .btn--ghost-dark{border-color:rgba(2,77,104,.42);color:var(--ink)}
.section--accent .btn--ghost:hover,.section--accent .btn--ghost-dark:hover{border-color:var(--brand);color:var(--brand)}
/* The photo is masked out instead of being covered by a flat amber scrim: with
   a graded ground a solid scrim colour only matches at one point across the
   band and seams everywhere else. Masking lets the real ground show through. */
.section--accent .router__media::after{content:none}
.section--accent .router__media img{
  -webkit-mask-image:linear-gradient(90deg,transparent 0%,#000 46%);
  mask-image:linear-gradient(90deg,transparent 0%,#000 46%);
}
@media (max-width:900px){
  .section--accent .router__media img{
    -webkit-mask-image:linear-gradient(180deg,transparent 0%,#000 42%);
    mask-image:linear-gradient(180deg,transparent 0%,#000 42%);
  }
}

/* reviews band re-tinted from grey to the brand blue family. This one keeps a
   ramp rather than a glow — it has to fade out into the page below it. */
.reviews-band{
  background-color:var(--brand-50);
  background-image:linear-gradient(180deg,var(--brand-50) 0%,var(--brand-25) 55%,var(--surface) 100%);
}
.reviews-band::before{background-image:radial-gradient(rgba(2,77,104,.09) 1px,transparent 1px)}
.reviews-mark{color:rgba(2,77,104,.07)}


/* ==========================================================================
   Closing CTA — one band per page
   Pages used to end with a footnote CTA row, then a dark .call-band, then a
   dark .cta-band: three asks in a row, the last two on the same ground so
   they read as one long block. .closer folds the phone and the booking into
   a single band on the deepest ground, and it is the only place the amber
   button appears on a page.
   ========================================================================== */
/* Bottnen är LOGOTYPBLÅ --brand sedan 1 augusti, inte den djupa --brand-900.
   De tre mosaikbanden ska läsas som en familj, och avsiktsväxelns ton är den
   som valdes. Reservläget utan WebGL blir en aning ljusare än förut, men det
   är fortfarande en av sajtens egna bottnar och inte en ny färg. */
.closer{background-color:var(--brand);color:var(--dark-fg);padding:var(--pad-md) 0;position:relative;overflow:hidden}
.closer>*:not(.deco):not(.tilebg){position:relative;z-index:1}
.closer__inner{
  display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);
  gap:48px var(--pad-md);align-items:center;
}
.closer__body{display:flex;flex-direction:column;gap:14px;align-items:flex-start}
.closer__body .eyebrow{color:var(--accent)}
.closer__body p{color:var(--dark-body);text-wrap:pretty}
.closer__actions{
  display:flex;flex-direction:column;gap:12px;align-items:stretch;
  width:100%;max-width:340px;margin-left:auto;
}
/* #8FB6C6 låg på 4,27:1 mot mosaiken, alltså redan under AA:s 4,5 för
   brödtext, och mot den ljusare bandbilden föll den till 2,92. Uppmätt, inte
   uppskattad: se noten vid canvas.tilebg. #E3EFF4 ger 4,62:1 mot bandets
   ljusaste punkt under knappraden. Samma familj, bara ljusare. */
.closer__actions .note{color:#E3EFF4;text-align:center;font-size:14px}

/* ==== begin: closer/shader ====
   Rutnätsmattan bakom bandet, och knapparna som mäts ur den.

   Duken och skärmen bär .deco, vilket är klassen som redan finns för
   bakgrundslager: den tar dem ur flödet och lägger dem på z-index 0, så att
   .closer>*:not(.deco) inte drar upp dem bredvid texten. Utan den hade de
   hamnat på z-index 1 tillsammans med innehållet.

   Ordningen i bakgrunden är duk, skärm, innehåll. Skärmen finns för att
   mattan går ljus i vissa lägen och rubriken annars hade legat på en vit
   platta. Den är tät till vänster där texten står och borta till höger där
   knapparna står, för en jämn skärm hade dolt hela mönstret. */
/* .tilebg är duken och slöjan på BÅDA banden, .closer och .router.

   .deco hade passat, men den är avstängd: se raden .deco{display:none} längre
   ned, kvar sedan vattenstämplarna ströks.

   Undantaget måste stå i VARJE regel som lyfter direkta barn till z-index 1,
   inte bara i bandets egen. Det kostade en felsökning: .router bär också
   .section--dark, och den grundens regel .section--dark>*:not(.deco) satte
   position:relative på duken, som därmed gick tillbaka in i flödet och lade
   720 px under bandet. Alla sex grundregler undantar den nu. Läggs ett tredje
   band till på en grund som inte står med, upprepas det felet: sektionen blir
   plötsligt dubbelt så hög och duken hamnar ovanför texten. */
/* .tilebg står definierad en gång, vid .router. */
.closer__gl{width:100%;height:100%;display:block}
/* Slöjan är i bandets EGEN blå, inte i den djupa: den ska dra mosaiken mot
   #024D68 och inte mot något mörkare, annars tappar bandet sin ton. Tät till
   vänster där texten står, tunn till höger där knapparna står. Den går inte
   ned till noll längre: på den ljusare bottnen behöver de ljusaste dragen i
   driften fortfarande hållas tillbaka bakom den vita etiketten. */
.closer__scrim{
  background:linear-gradient(96deg,
    rgba(2,77,104,.90) 0%,rgba(2,77,104,.84) 34%,rgba(2,77,104,.62) 56%,
    rgba(2,77,104,.40) 78%,rgba(2,77,104,.34) 100%);
}
/* Färgen sätts längre ned, se .closer__body .eyebrow vid ground-reglerna. */

/* KNAPPMÅTTEN KOMMER FRÅN js/cta.js och ingen annanstans ifrån. Skriptet
   räknar dem ur shaderns rutnät: höjden är två plattor plus fogen, radien är
   plattans egen, mellanrummet är en tom plattrad, bredden är den bredaste
   knappens naturliga bredd upprundad till nästa hela platta.

   Reservvärdena här gäller bara de sekunder innan skriptet hunnit köra, och
   om det aldrig kör. De är .btn:s vanliga mått. */
/* Bredd och höjd sätts av js/tiles.js på elementen själva, ur data-tile-match
   och data-tile-h. Kvar här är bara det som inte är ett mått i plattor. */
.closer .closer__buttons .btn{padding-block:0;border-radius:var(--tile-r,var(--radius-sm))}
/* Den genomskinliga knappen står med bara en ram över mattan, och innanför
   ramen syns exakt två plattrader. Etiketten bär sin egen kontrast i en
   skugga i stället för i en platta bakom sig, för en platta hade dolt just
   den mosaik som knappen är till för att visa. */
/* GLASET, samma behandling som avsiktsväxelns kort. Knappen var en ren ram
   med etiketten stående direkt på driften; nu är den en ljus panel ovanpå
   mosaiken med oskärpa innanför kanten. Mönstret går suddigt inuti och skarpt
   utanför, så kanten blir tydlig av sig själv, och etiketten får en yta att
   stå på i stället för en textskugga som ska bära hela kontrasten.

   Skuggan står kvar men svagare: den behövs fortfarande i det ljusaste läget i
   driften, där glaset suddar något som är nästan vitt. */
.closer .btn--ghost-dark,
.router .btn--ghost-dark{
  background:rgba(255,255,255,.09);
  border-color:rgba(255,255,255,.30);
  backdrop-filter:blur(6px) saturate(1.3);
  -webkit-backdrop-filter:blur(6px) saturate(1.3);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.24);
  text-shadow:0 1px 2px rgba(2,44,60,.55);
}
.closer .btn--ghost-dark:hover,
.router .btn--ghost-dark:hover{
  background:rgba(255,255,255,.17);border-color:rgba(255,255,255,.50);
}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .closer .btn--ghost-dark,
  .router .btn--ghost-dark{background:rgba(255,255,255,.17)}
}
@media (max-width:900px){
  /* En lodrät skärm i stället för en vågrät: i en spalt står texten ovanför
     knapparna, inte till vänster om dem, så gradienten ska gå nedåt. */
  .closer__scrim{background:linear-gradient(180deg,rgba(2,77,104,.90),rgba(2,77,104,.62))}
}
/* ==== end: closer/shader ==== */
.closer__mark{
  right:-.06em;bottom:-.34em;
  font-size:clamp(220px,28vw,400px);
  color:rgba(60,178,221,.055);
}
@media (max-width:900px){
  .closer__inner{grid-template-columns:minmax(0,1fr);gap:28px}
  .closer__actions{max-width:none;margin-left:0}
}

/* Amber primary — the 10%. Used for the single most important action on a
   page (the closing band) and for the header CTA once the bar goes solid. */
/* Flat, not glowing. The accent button used a gradient face plus a 26px yellow
   halo that grew on hover — that combination is what makes a CTA read as an ad
   rather than as part of the page. A flat accent field with a tight neutral
   shadow stays the loudest thing on the band without the halo.
   The blue primary carried the same device and is toned to match further down;
   flattening one and not the other would stop the site reading as one system. */
.btn--accent{background:var(--accent);color:var(--ink);box-shadow:0 1px 2px rgba(1,44,60,.14)}
.btn--accent:hover{background:var(--accent-hover);color:var(--ink);box-shadow:0 2px 4px rgba(1,44,60,.18);transform:translateY(-1px)}
.btn--accent:active{transform:translateY(0)}
.closer .btn--accent,.section--deep .btn--accent,.section--dark .btn--accent,
.section--blue .btn--accent,.hero .btn--accent{background:var(--accent);color:var(--ink)}
.closer .btn--accent:hover,.section--deep .btn--accent:hover,.section--dark .btn--accent:hover,
.section--blue .btn--accent:hover,.hero .btn--accent:hover{background:var(--accent-hover);color:var(--ink)}

/* ==========================================================================
   Numbers must not break
   "12 787 kr" was wrapping between the figure and the unit, and between the
   thousands groups. Every figure in the design is short enough to hold one
   line at its own clamp floor, so they are all set to stay on one.
   ========================================================================== */
.savings__figure,.compare__figure,.bignum__fig,.prod__val,
.total__fig,.freq__value,.spec__row span:last-child,.kv dd,.rrow span:last-child{
  white-space:nowrap;
}

/* ==========================================================================
   Spacing utilities
   These replace ~90 repeated style="margin-top:…" attributes in the markup.
   ========================================================================== */
.mt-6{margin-top:6px}.mt-16{margin-top:16px}.mt-20{margin-top:20px}
.mt-24{margin-top:24px}.mt-28{margin-top:28px}.mt-32{margin-top:32px}
.mt-40{margin-top:40px}.mt-56{margin-top:56px}.mt-72{margin-top:72px}
.mb-8{margin-bottom:8px}.mb-12{margin-bottom:12px}.mb-16{margin-bottom:16px}
.mb-20{margin-bottom:20px}.mb-24{margin-bottom:24px}.mb-28{margin-bottom:28px}
.mb-32{margin-bottom:32px}.mb-40{margin-bottom:40px}.mb-48{margin-bottom:48px}
.mb-56{margin-bottom:56px}
.pt-0{padding-top:0}
.self-start{align-self:start}
.items-center{align-items:center}
.push-right{margin-left:auto}

/* Icon sized to sit on a text baseline — replaces the check <path> that was
   pasted inline 40+ times. */
.ico{width:15px;height:15px;flex:none;transform:translateY(2px)}
.ico--lg{width:18px;height:18px;transform:none}

/* Inline strip: eyebrow + figure + copy + link on one rule */
.strip{display:flex;flex-wrap:wrap;align-items:baseline;gap:16px 40px}
.strip--center{align-items:center;justify-content:space-between}

/* ==========================================================================
   Motion, part two — the rest of the site
   Everything below reuses the existing .js/[data-reveal] observer, so no new
   script weight. These are the state transitions that were still snapping.
   ========================================================================== */

/* Nav dropdown: was display none/block */
.nav-menu{
  display:block;opacity:0;visibility:hidden;transform:translateY(-6px) scale(.99);
  transform-origin:top right;
  transition:opacity .2s var(--ease-out),transform .22s var(--ease-out),visibility .22s;
}
.nav-menu[data-open="true"]{opacity:1;visibility:visible;transform:none}
.nav-menu a{transition:background-color .15s ease,color .15s ease,padding-left .2s var(--ease-out)}
.nav-menu a:hover{padding-left:22px}

/* Mobile panel fades in rather than appearing */
.site-header__panel[data-open="true"]{animation:fadeUp .28s var(--ease-out) both}

/* Accordion: height instead of display, where the browser can do it. Both
   states have to be display:grid or the row track has nothing to animate
   between — the display:none/block pair above stays as the fallback. */
@supports (grid-template-rows:0fr){
  .accordion__panel,.accordion__panel[data-open="true"]{display:grid}
  .accordion__panel{
    grid-template-rows:0fr;padding-bottom:0;
    transition:grid-template-rows .38s var(--ease-out),padding-bottom .38s var(--ease-out);
  }
  /* A 0fr track still can't shrink below its item's padding, so the closing
     gap moves from the paragraph onto the panel and animates with it. */
  .accordion__panel>*,.accordion__panel>p{overflow:hidden;padding-bottom:0}
  .accordion__panel[data-open="true"]{grid-template-rows:1fr;padding-bottom:28px}
  @media (max-width:640px){.accordion__panel[data-open="true"]{padding-bottom:24px}}
}

/* Tab underline slides in instead of cutting */
.tab::after{
  content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--accent);
  transform:scaleX(0);transform-origin:left;transition:transform .3s var(--ease-out);
}
.tab[aria-selected="true"]::after{transform:scaleX(1)}
.tabpanel[data-open="true"]{animation:fadeUp .35s var(--ease-out) both}

/* Filters, spec rows, list rows: small feedback on press and hover */
.filter{transition:border-color .2s ease,background-color .2s ease,color .2s ease,transform .2s var(--ease-out)}
.filter:active{transform:scale(.97)}
.rule-list li,.reason-list li,.checklist li,.timeline li{transition:border-color .3s ease}
.rule-list li:hover,.reason-list li:hover{border-top-color:var(--hair-strong)}
.kv>div{transition:background-color .2s ease}
.kv>div:hover{background:rgba(2,77,104,.03)}
.lan li{transition:color .2s ease,padding-left .2s var(--ease-out)}
.lan li:hover{color:var(--ink);padding-left:4px}
.contact-card a{transition:color .2s ease}
.contact-card a:hover{color:var(--brand-600)}
.site-footer ul a{transition:color .2s ease,padding-left .2s var(--ease-out)}
.site-footer ul a:hover{padding-left:4px}
.site-header__logo img,.site-footer__logo img{transition:opacity .25s ease}
.site-footer__logo:hover img{opacity:.75}
.stars svg{transition:transform .3s var(--ease-out)}
.rating-badge:hover .stars svg,.hero__rating:hover .stars svg{transform:scale(1.12)}
.meter span{transform-origin:left;animation:meterIn 1.1s var(--ease-out) both}
@keyframes meterIn{from{transform:scaleX(0)}to{transform:scaleX(1)}}

/* Page heads get the hero's staggered entrance */
.page-head [data-enter]{animation-delay:calc(var(--i,0) * 110ms + 80ms)}

/* ==========================================================================
   Team — who works here and what they do (om-oss)
   Every seat is an initials monogram. A photo is an <img> laid over it that
   deletes itself if the file is missing, so the monogram is both the fallback
   and the intended look for someone who has no headshot yet. That is what lets
   a founder, a new hire and an unfilled seat sit in the same grid without any
   of them looking broken.

   To add a face: drop the file in assets/img/ and add the <img> to that person.
   ========================================================================== */
.team{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:32px 24px}
.person{display:flex;flex-direction:column;align-items:flex-start;gap:14px}
.person__photo{
  position:relative;overflow:hidden;
  display:grid;place-items:center;
  width:104px;height:104px;flex:none;border-radius:50%;
  background-color:var(--brand-50);
  box-shadow:inset 0 0 0 1px var(--brand-100);
  font-family:var(--fig);font-size:22px;font-weight:500;letter-spacing:.02em;
  color:var(--brand-600);user-select:none;
}
.person__photo img{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center top;
}

.person__name{font-size:var(--fs-h3);font-weight:600;letter-spacing:-.02em;line-height:1.25}
.person__role{
  margin-top:2px;font-family:var(--sans);font-size:var(--fs-mono);
  font-weight:500;color:var(--brand-600);
}
.person p{margin-top:6px;font-size:16px;color:var(--ink-muted);text-wrap:pretty}
.person a{font-size:15px;color:var(--ink-muted);text-decoration:none}
.person a:hover{color:var(--brand)}

/* An open seat. Dashed ring and muted type so it reads as a vacancy at a
   glance rather than as a person whose photo failed to load. */
.person--open .person__photo{
  background-color:transparent;box-shadow:none;
  border:1px dashed var(--hair-strong);color:var(--ink-faint);font-size:26px;
}
.person--open .person__name,.person--open .person__role{color:var(--ink-faint)}
.person--open .person__role{font-weight:400}

@media (max-width:860px){.team{grid-template-columns:repeat(2,minmax(0,1fr));gap:28px 20px}}
@media (max-width:520px){
  .team{grid-template-columns:minmax(0,1fr);gap:24px}
  .person{flex-direction:row;align-items:center;gap:16px}
  .person__photo{width:76px;height:76px;font-size:18px}
  .person p{display:none}
}

@media (prefers-reduced-motion:reduce){
  .nav-menu{transition:none}
  .accordion__panel{transition:none}
  .tab::after{transition:none}
  .tabpanel[data-open="true"],.site-header__panel[data-open="true"]{animation:none}
  .meter span{animation:none}
  .btn--accent:hover{transform:none}
}

/* ==== added: placeholder-system ==== */
/* --- Marked placeholders --------------------------------------------------
   A slot whose real figure does not exist yet. Deliberately visible: it has to
   be impossible to ship one of these by accident, and impossible to mistake one
   for a real number. Every occurrence is also tagged with an HTML comment
   beginning "PLACEHOLDER:" so `grep -rn "PLACEHOLDER:"` finds the full set.
   Delete the wrapper, not just the text, when the real value lands. */
.ph{
  display:inline-block;
  font-family:var(--fig);
  font-size:.86em;
  line-height:1.35;
  letter-spacing:.01em;
  color:var(--brand-800);
  background-color:var(--accent-50);
  border:1px dashed var(--accent-shade);
  border-radius:var(--radius-sm);
  padding:.15em .5em;
  text-transform:none;
}
/* On a dark or amber ground the amber-on-amber version disappears, so the
   marker inverts rather than opting out of being visible. */
.section--blue .ph,.section--dark .ph,.section--deep .ph,.closer .ph,.hero .ph,.router:not(.section--accent) .ph{
  color:var(--accent);
  background-color:rgba(255,192,0,.10);
  border-color:rgba(255,192,0,.55);
}
.section--accent .ph{
  color:var(--brand-900);
  background-color:rgba(255,255,255,.55);
  border-color:var(--brand-800);
}
/* ==== end: placeholder-system ==== */

/* ==== added: hero-trust-rail ==== */
/* --- Hero trust rail ------------------------------------------------------
   Three claims above the H1, each a two-line stack. Sits where a single
   unsourced "300+ nöjda kunder" chip used to: the slot was doing real work,
   so it was rebuilt rather than deleted. Rules between items rather than
   dots, so it reads as a row of facts and not as a list of features. */
.hero__trust{
  display:flex;flex-wrap:wrap;align-items:stretch;
  gap:0 clamp(18px,2.2vw,30px);
  margin-bottom:clamp(24px,2.6vw,34px);
}
/* Ett påstående per objekt, en rad. Kolumnriktningen och b/span-reglerna
   fanns för underraderna, som är borttagna. */
.hero__trust-item{
  padding-left:clamp(18px,2.2vw,30px);
  border-left:1px solid rgba(255,255,255,.22);
  text-decoration:none;
  font-family:var(--sans);font-size:15px;font-weight:600;
  letter-spacing:-.005em;color:#fff;
}
.hero__trust-item:first-child{padding-left:0;border-left:0}
/* Den första går någonstans, så den är den enda med hover. */
a.hero__trust-item:hover{text-decoration:underline;text-underline-offset:3px}
a.hero__trust-item:focus-visible{outline-offset:6px;border-radius:var(--radius-sm)}

/* Ingen egen brytpunkt här. Blocket på max-width:760 längre ned i filen gör
   redan raden till en radbrytande rad med mittpunkter emellan, vilket är en
   bättre lösning på smal skärm än en staplad kolumn. Det blocket skrevs för
   att dölja underraderna på telefon; nu när de är borta överallt gör det bara
   den del som fortfarande skiljer sig. */
/* ==== end: hero-trust-rail ==== */

/* ==== added: reviewbar-track ==== */
/* Track-record line under the review note. Sits between the note and the link
   because it is the same kind of claim: a fact about us, not a quote from a
   customer. */
.reviewbar__track{
  margin-top:14px;
  font-size:var(--fs-mono-sm);
  color:var(--ink-muted);
}
/* ==== end: reviewbar-track ==== */

/* ==== added: hero-scrim ==== */
/* --- Hero scrim, rebuilt --------------------------------------------------
   The old scrim was vertical only, and its lightest band (.47) sat exactly
   where the H1 does. On this photo that band is a sunlit roof and a white
   window frame, so "kostnad." was landing on the brightest pixels in the
   picture.

   Two layers instead, the way the strongest heroes in this market do it:
   a horizontal fade that reaches near-solid brand blue on the type side and
   releases by ~70% so the roof is still a photograph, plus a much gentler
   vertical darkening to seat the header and hand off to the section below.
   Painted as background-image, not the shorthand, per the ground rule.

   HÖGERSIDAN ÄR ÖPPEN, i tre steg på Julius besked. De två första var för
   små och han sa det rakt ut. Slutläget:

     vågrätt   .93 0%,  .86 26%,  .48 50%,  .02 66%,  0 80%
     lodrätt   .62 0%,  .08 38%,  .14 72%,  .78 100%

   TVÅ LAGER MÅSTE SÄNKAS, INTE ETT, och det är hela knepet. Det vågräta
   lagret kan gå till noll, men under det ligger ett lodrätt lager som är
   detsamma över hela bredden, och DET blir golvet så fort det vågräta släppt.
   Att bara dra det vågräta mot noll flyttade täckningen längst ut från 28 till
   25 till 21 procent, alltså knappt märkbart, för golvet låg kvar. Det lodräta
   mellanbandet är därför nere på .08 och .14, från .22 och .30.

   Att sänka ett lager som täcker hela bredden låter som att det skulle ljusa
   upp texten också. Det gör det inte: där ligger det vågräta på .93, och 1
   minus 0,07 gånger något blir 94 procent vare sig detta något är .70 eller
   .92. Sänkningen syns nästan bara där det vågräta redan släppt.

   Samlad täckning halvvägs ned, ursprung och de tre stegen:

       0 %   94,7  94,7  94,4  93,6    rubrikens vänsterkant
      30 %   85,5  84,8  84,0  81,5    knapparna
      46 %   67,6  65,9  64,4  58,6    ingressens sista rad slutar här
      72 %   35,9  31,3  25,4  10,3    taket
     100 %   28,2  25,2  20,6   9,2    bildens högerkant

   Alltså: rubriksidan har rört sig en procentenhet på tre steg, högersidan har
   gått från 28 till 9. Det var det som efterfrågades.

   ÄNDSTOPPEN ÄR ORÖRDA. Det lodräta lagrets .62 uppe och .78 nere sätter
   sidhuvudets botten och överlämningen till avsnittet under; de har inget med
   högersidan att göra och ska inte dras med. Uppe till höger, där utility-baren
   ligger, går täckningen från 59,7 till 59,2 procent.

   MOBILREGELN LÄNGRE NED ÄR ORÖRD genom alla tre stegen. Där går texten över
   hela bredden och det vågräta lagret har ingenstans att släppa. */
.hero__scrim{
  background-image:
    linear-gradient(90deg,
      rgba(1,44,60,.93) 0%,
      rgba(1,44,60,.86) 26%,
      rgba(1,44,60,.48) 50%,
      rgba(1,44,60,.02) 66%,
      rgba(1,44,60,0) 80%),
    linear-gradient(180deg,
      rgba(1,44,60,.62) 0%,
      rgba(1,44,60,.08) 38%,
      rgba(1,44,60,.14) 72%,
      rgba(1,44,60,.78) 100%);
}
@media (max-width:900px){
  /* One column, so the type crosses the full width: the horizontal layer has
     nowhere to release to and the work goes back to the vertical one. */
  .hero__scrim{
    background-image:
      linear-gradient(90deg,rgba(1,44,60,.62) 0%,rgba(1,44,60,.42) 100%),
      linear-gradient(180deg,
        rgba(1,44,60,.72) 0%,
        rgba(1,44,60,.44) 34%,
        rgba(1,44,60,.62) 70%,
        rgba(1,44,60,.88) 100%);
  }
}
/* ==== end: hero-scrim ==== */

/* router-bleed är borttagen 1 augusti. Den lät fotot falla ut mot
   skärmkanten, vilket var rätt när bilden var en utfallande yta. Nu är den en
   ruta med hela plattor runt om, och en ruta som sticker ut ur sitt eget
   rutnät är just det fel blocket byggdes för att undvika. */

/* ==== added: kv-prose ==== */
/* --- .kv: stop a long value forcing horizontal scroll ----------------------
   .kv is the site's key/value component and its value slot is set in the mono
   face with tabular numerals, because it was built for figures. Nothing
   stopped a flex item from refusing to shrink, so the long "Uppgifter" value
   on integritetspolicy.html pushed the document to 1596px at a 1440 viewport
   and the whole page scrolled sideways. min-width:0 lets the value wrap; the
   row wraps to two lines before the value gets unreadably narrow. */
.kv>div{flex-wrap:wrap}
.kv dt{min-width:0;flex:0 1 auto}
.kv dd{min-width:0;flex:1 1 260px;text-align:right;overflow-wrap:anywhere}

/* Prose values are not figures, so they do not belong in the numeric face.
   Used by the two policy pages, where the value is a sentence rather than a
   number; lonsamhet.html and the company details on villkor.html keep mono. */
.kv--prose dd{
  font-family:var(--sans);
  font-variant-numeric:normal;
  color:var(--ink);
  overflow-wrap:normal;
}
@media (max-width:640px){
  /* Below this the two-column read stops working: the value wraps to three or
     four words a line against a label that is already short. Stack instead. */
  .kv>div{gap:2px 16px}
  .kv dt{flex:1 0 100%}
  .kv dd{flex:1 0 100%;text-align:left}
}
/* ==== end: kv-prose ==== */
/* ==== added: chrome ==== */
/* --- The overlay header lost its transparency ------------------------------
   `.site-header--overlay{background:transparent}` sits at the top of the file;
   a later `.site-header{background-color:var(--surface-raised)}` has the same
   specificity, so the longhand won and every hero page painted a white bar
   over the photograph with a white logo on it. Restated here, after it.
   The stuck state carries [data-stuck] and outranks this, so the scroll swap
   is untouched. */
.site-header--overlay{background-color:transparent}

/* --- Utility bar: proof left, the fastest human route right ----------------
   Svea Solar runs a Trustpilot score and a tel: link in the same 38px strip
   and it is the hardest-working part of their chrome: the rating is visible
   at every scroll position on every page instead of once, 5 800px down.
   The figure is the site's existing one, not a new claim. */
.utility__inner{gap:16px}
.utility__rating{display:inline-flex;align-items:center;gap:8px;color:inherit;white-space:nowrap}
.utility__rating b{font-weight:600;color:#fff}
.utility__star{width:13px;height:13px;flex:none;fill:var(--accent)}
.utility__count{opacity:.7}
.utility__rating:hover .utility__count{opacity:1}
.utility__contact{display:flex;align-items:center;gap:22px;white-space:nowrap}
.utility__hours,.utility__mail{opacity:.8}
.utility__tel{display:inline-flex;align-items:center;gap:7px;color:#fff;font-weight:500}
.utility__tel svg{width:14px;height:14px;flex:none}

/* Six top-level items plus a longer CTA label. The gap tightens rather than
   the type shrinking, so the bar never reads as two different sizes. */
@media (max-width:1280px){
  .site-nav ul{gap:20px}
  .site-header__inner{gap:16px}
}

/* The bar used to vanish below 1100px, which left a phone-shaped company with
   no phone number on a phone. It stays, carrying only the two things worth the
   32px: the rating and the number. On a hero page it sits over the photograph,
   so the header keeps exactly one hairline rather than two. */
@media (max-width:1100px){
  .utility{display:block}
  .utility__inner{min-height:32px;gap:12px;font-size:13px}
  .utility__count,.utility__hours,.utility__mail{display:none}
  .utility__star{width:12px;height:12px}
  .utility__tel svg{width:13px;height:13px}
  .site-header--overlay .utility{border-bottom:0}
  .site-header__panel__meta a,.site-header__panel__meta span{display:inline-flex;align-items:center;gap:9px;color:var(--ink-muted);text-decoration:none}
  .site-header__panel__meta svg{width:15px;height:15px;flex:none;color:var(--ink-faint)}
}
@media (max-width:640px){
  .utility__inner{flex-wrap:nowrap;padding-top:0;padding-bottom:0;gap:12px}
}

/* --- Footer: four groups, not a site index --------------------------------
   The twelve-link "Sidor" column was a sitemap with a heading on it. Split by
   what the reader is asking (the purchase / the company / how to reach us) it
   fills the width instead of leaving a third of the footer empty, and the
   contact group gains the two things a buyer checks and the site was not
   showing: opening hours and where the rating comes from. */
.site-footer__grid{
  grid-template-columns:minmax(0,1.5fr) minmax(0,1fr) minmax(0,.92fr) minmax(0,1.08fr);
  gap:40px var(--pad-md);align-items:start;
}
.site-footer__brand{max-width:36ch}
.site-footer .site-footer__col ul{display:flex;flex-direction:column;gap:11px}
.site-footer .site-footer__col li{line-height:1.45}
.site-footer__rating{
  display:inline-flex;flex-direction:column;gap:3px;
  margin-top:20px;font-family:var(--sans);font-size:14px;
  color:var(--ink-faint);text-decoration:none;
}
.site-footer__rating b{font-size:15px;font-weight:600;color:var(--ink)}
.site-footer__rating:hover span{color:var(--ink-muted)}
.site-footer__contact li{display:grid;grid-template-columns:16px minmax(0,1fr);gap:11px;align-items:start}
.site-footer__contact li svg{width:16px;height:16px;flex:none;margin-top:3px;color:var(--ink-faint)}
.site-footer__contact a,.site-footer__contact span{font-size:15px;line-height:1.45;color:var(--ink-muted);text-decoration:none}
.site-footer__contact a:hover{color:var(--ink)}
@media (max-width:860px){
  .site-footer__grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:36px 24px}
  .site-footer__brand{grid-column:1 / -1;max-width:46ch}
  .site-footer ul a{font-size:14px}
  .site-footer__contact a,.site-footer__contact span{font-size:14px}
}

/* --- Closing CTA: buttons sized to their labels ---------------------------
   The two buttons stretched to the full 340px of their column, which turned a
   phone number into a slab and left the bottom right of the band empty. They
   now size to the wider of the two and sit right-aligned as one object, and
   the note runs the width of the column beneath them, so the right side is
   about as tall as the copy on the left. The band also closes tighter than it
   opens, matching the rhythm every other band on the site now uses. */
.closer{padding:var(--pad-md) 0 calc(var(--pad-md) * .82)}
.closer__actions{width:100%;max-width:none;margin-left:0;align-items:stretch;gap:16px}
/* MELLANRUMMET ÄR EN TOM PLATTRAD, inte 12 px. Det stod tidigare på två
   ställen: reservvärdet i tile-blocket längre upp och 12 px här, och den här
   raden ligger senare i filen och vann. Knapphöjden är 1,895 plattor och
   mellanrummet 1,105, alltså exakt 3 plattor från knapp till knapp; med 12 px
   blev delningen 57,48 px mot plattans 24, och den övre knappen låg 9,48 px
   bredvid rutnätet vid varje bredd. Den undre satt rätt, för det är den
   justeringen mäter på. Ett mått får sättas på ett ställe. */
.closer__buttons{display:flex;flex-direction:column;gap:var(--tile-gap,12px);width:fit-content;margin-left:auto}
.closer__actions .note{text-align:right;color:#E3EFF4}
@media (max-width:900px){
  .closer__buttons{width:auto;margin-left:0}
  .closer__actions .note{text-align:left}
}
/* ==== end: chrome ==== */

/* ==== added: system ==== */
/* ==========================================================================
   Design-system pass
   Two live bugs, then seven things the site was not yet doing that the
   benchmark set does: contrast on the faint tier, three weights enforced,
   display tracking, a rule for the label/content split, intrinsic buttons,
   amber reserved for action, and zero box-shadow. Nothing new is introduced;
   every rule below either fixes, tightens or removes.
   ========================================================================== */

/* --- BUG: the mobile process rail never applied ---------------------------
   [HISTORIK: @keyframes trackNode togs bort 4 augusti 2026 när rälsen gjordes
   helfärgad, så felet nedan finns inte längre. Beskrivningen står kvar för att
   felsökningen är värd att kunna läsa igen — en enda extra klammer åt en hel
   media-fråga tyst.]
   css/site.css had a stray "}" at the end of the @keyframes trackNode block
   (it read "}to{color:var(--ink)}}"). Top-level CSS recovery turns that
   brace into the start of a bogus qualified rule, whose prelude then eats the
   comment AND the whole `@media (max-width:900px)` block that follows — so the
   .track rail's mobile layout was dropped on the floor. At 390 the start page
   rendered four 65px columns of overlapping text with the fourth running off
   the right edge, and it forced 11px of horizontal scroll on index.html.
   Restated here verbatim so the rail is vertical again on a phone.
   The stray brace itself is listed in handoffs; deleting it makes this block
   redundant but not wrong. */
@media (max-width:900px){
  .track{grid-template-columns:minmax(0,1fr);--gap:36px;--rail-y:9px}
  .track__step{padding-left:34px;padding-top:0}
  .track__line{
    left:5.5px;top:var(--rail-y);margin-top:0;
    width:2px;height:calc(100% + var(--gap));
  }
  .track__line::after{transform-origin:center top}
  .track__foot{margin-top:36px}
}

/* --- BUG: a prose value in .kv forced 141px of horizontal scroll ----------
   .kv dd is in the "numbers must not break" list, which sets white-space
   nowrap. That is right for "12 787 kr" and wrong for a sentence: on
   integritetspolicy.html the Uppgifter row ran to 511px inside a 350px
   column and the document scrolled sideways at 390. .kv--prose already says
   "this value is a sentence"; it just needs to say it about wrapping too. */
.kv--prose dd{white-space:normal}

/* --- Contrast: the faint tier was failing --------------------------------
   #7C8E96 on --surface is 3.08:1. It carries .note, .faint, every figcaption,
   the calculator assumptions, the review note and both policy pages, all at
   13–15px, so it was the single widest contrast failure on the site.
   #5A6C73 measures 4.95:1 on --surface, 5.49:1 on white and 4.78:1 on --tint,
   and still sits a clear step below --ink-muted (6.72:1) so the three-tier
   type hierarchy survives. */
:root{--ink-faint:#5A6C73}

/* --- Three weights, actually three ---------------------------------------
   The file declares 400/500/600 and never 700, but a bare <b> in the article
   prose, in the calculator assumptions and in the lonsamhet copy inherited
   the UA's bold. Those were rendering at 700 against a 600 headline. */
b,strong{font-weight:600}

/* --- Display tracking -----------------------------------------------------
   .h1 was already at -.03em and the hero at -.035em; everything below h1 was
   left at -.02em, so the h2 at 48px read looser than the h1 above it. Otovo
   scales tracking with size across its whole ramp (-1.44px at 48, -1.08px at
   36, -0.6px at 24 — a flat -.03em). Matching that is most of what makes a
   heading read expensive rather than templated. */
.h2{letter-spacing:-.028em}
.h2--sm{letter-spacing:-.026em}
.h3,.h4,.accordion__title,.tab,.person__name,.contact-card a,.contact-card p,
.intent__label,.track__title,.analys__title,.hcard__name,
.artcard__title,.bento__item--feature .artcard__title{letter-spacing:-.022em}

/* --- The label/content split ---------------------------------------------
   The single most visible rhythm fault: a two-column split whose left column
   is an eyebrow plus one short heading and whose right column is a long list.
   align-items:start left a 250–320px hole under the heading on boka.html,
   lonsamhet.html, tack.html, villkor.html, integritetspolicy.html and
   produkter.html. Centring splits that whitespace above and below the
   heading, so it reads as an editorial pairing instead of a gap.

   Two opt-outs already exist in the markup and both keep working:
   .split--end (om-oss) and .self-start on the child, used on varfor-oss where
   the right column is a 1200px accordion and a centred heading would float in
   the middle of nowhere. Those get the other half of the rule below.
   Only applied where the split is actually two columns. */
@media (min-width:981px){
  .split,.split--narrow,.split--tight{align-items:center}
  .split--end{align-items:end}
  /* Where the content column is very tall, centring is the wrong answer and
     top-alignment leaves a 1000px void. The label travels with the reading
     instead. Same device as a sticky table of contents; it costs nothing and
     it is why the two long USP sections on varfor-oss no longer read as one
     column of text beside one column of nothing. */
  .split > .self-start{position:sticky;top:104px}
}
/* position:sticky needs a scrolling ancestor, and overflow:hidden makes every
   glow band its own (unscrollable) scroll container, which would silently
   kill the rule inside .section--dark. overflow:clip clips identically
   without establishing one. */
@supports (overflow:clip){
  .section--dark,.section--blue,.section--deep,.closer,.router,
  .reviews-band{overflow:clip}
}
@media (min-width:861px){
  .reviewbar__inner{align-items:center}
}
/* The statement pair is a grid, so both halves are already the same height —
   but whichever half has the shorter copy carried 110–150px of empty card at
   the bottom (the paper one on index, the blue one on produkter). Centring
   the content makes them read equal instead of merely measure equal. */
.statement{justify-content:center}
/* The feature card spans two bento rows, so its 4:3 photo left a void above
   the byline. The photo takes the extra row instead. */
.bento__item--feature .artcard .shot{flex:1 1 auto;aspect-ratio:auto;min-height:220px}

/* --- Banded sections close tighter than they open -------------------------
   Symmetric --pad-xl put 112px under the last line of every band, which is
   what reads as slack. Otovo runs 96/64 and 128/64 on its bands; the extra
   room belongs above a heading, not below a paragraph. */
.section{padding:var(--pad-xl) 0 calc(var(--pad-xl) * .8)}
.section--lg{padding:var(--pad-lg) 0 calc(var(--pad-lg) * .8)}
.section--md{padding:var(--pad-md) 0 calc(var(--pad-md) * .8)}
.reviewbar{padding:var(--pad-sm) 0 calc(var(--pad-sm) * .8)}

/* --- Buttons stay intrinsic ----------------------------------------------
   A button stretched to its container reads as a form control, not as a
   choice. All four benchmark sites keep them intrinsic at every width; ours
   went full-width below 640px and the closer's pair was a 340px stack on
   every screen, which made the most important CTA on the site look like a
   submit row. */
@media (max-width:640px){
  .btn-row{align-items:flex-start}
  .btn-row .btn{width:auto}
}
.closer__actions{align-items:flex-end;max-width:none}
.closer__actions .note{text-align:right}
@media (max-width:900px){
  .closer__actions{align-items:flex-start}
  .closer__actions .note{text-align:left}
}
/* The number at --fs-lead (24px) inside a ghost button outweighed the amber
   primary beside it, so the band had two loudest things. Same size as every
   other button label; the phone stays a secondary action. */
.btn--phone{font-size:17px;letter-spacing:.01em}

/* --- Amber means "click this" ---------------------------------------------
   Svea is ~95% greyscale with its gradient reserved for primary CTAs; Otovo's
   peach is on the submit arrow and essentially nowhere else. Ours had amber
   on decorative eyebrows, a pull-quote rule, a tag dot and a set of list
   marks. Each of those moves to the blue family; the accent keeps the closing
   CTA, the header CTA once stuck, the amber intent tile, the amber band, the
   rating stars, selection and every active/progress state. */
/* ÖGONBRYNET PÅ DE TVÅ SHADERBANDEN.

   Stod i --brand-300, #3CB2DD. Den färgen har ett jobb på sajten och det är
   inte det här: den markerar tillstånd i gränssnittet, hovrade ramar,
   batterisegmentet i energistapeln, kanten på ett citat. Som etikett över
   mosaiken läste den som en femte färg i ett band som redan har vitt, två
   blå och en gul.

   Nu vitt på 72 %, alltså exakt vad .section--blue .eyebrow redan gör på
   sajtens andra mörka grund. Etiketten blir en tyst inledning i stället för
   ett färgobjekt, och den gula knappen är kvar som bandets enda kulör. */
.closer__body .eyebrow{color:rgba(255,255,255,.72)}
.pull{border-left-color:var(--brand)}
.versus__col--good .versus__mark{color:#fff}


/* Oversized background glyphs (the "2×" behind the elbil paragraph, the ↗
   behind the closing CTA, the ” on the reviews band). None of the four
   benchmark sites carries a watermark like this, the ↗ sat directly under the
   closer's buttons, the 2× crossed four lines of body copy, and at 390 the ↗
   was one of the two things pushing index.html into horizontal scroll. */
.deco{display:none}

/* --- Zero box-shadow ------------------------------------------------------
   Otovo's whole system has none; 1KOMMA5° makes the image the card with a 1px
   border and nothing else; Svea's cards are white on grey with box-shadow
   none. Ours put --shadow-sm on eleven card types and lifted four of them on
   hover. The 1px hairline already separates every card from its ground, so
   the shadow was doing nothing except announcing itself. Hover feedback moves
   to the border, which is where 1KOMMA5° puts it. */
.card,.brand,.house,.video-card,.case,.contact-card,.freq,.figure-card,.calc,
.map-embed,.shot,.gallery figure,.mosaic figure,.journey__frame,.analys__stage,
.artcard,.calc-card,.getpanel,.statement--paper,.statement--blue,
.versus__col--bad,.versus__col--good,.gal__item,.rating-badge,.install__media,
.site-header--overlay[data-stuck="true"]{box-shadow:none}
.brand:hover,.house:hover,.video-card:hover,.contact-card:hover,.artcard:hover,
.gal__item:hover,.hcard:hover,.case:hover,.mosaic figure:hover,
.gallery figure:hover,.social:hover,.intent:hover{box-shadow:none;transform:none}
.brand:hover,.video-card:hover,.contact-card:hover,.artcard:hover,.hcard:hover,
.gal__item:hover,.mosaic figure:hover,.gallery figure:hover{border-color:var(--hair-strong)}
/* The pressed house card keeps its accent ring — that is a state, not depth. */
.hcard[aria-pressed="true"]{box-shadow:0 0 0 1px var(--accent)}
/* Buttons flatten with the cards. A drop shadow under a flat accent field is
   the last thing separating this from an ad unit. */
.btn--primary,.btn--accent{box-shadow:none}
.btn--primary:hover,.btn--accent:hover{box-shadow:none;transform:none}
.btn--primary:active,.btn--accent:active{transform:none}
/* The dropdown is a floating overlay rather than a card, so it keeps a
   shadow — toned into the blue family so nothing casts grey. */
.nav-menu{box-shadow:0 8px 24px rgba(1,44,60,.12)}
/* Rating stars stopped growing on hover: it is proof, not a control. */
.rating-badge:hover .stars svg,.hero__rating:hover .stars svg{transform:none}

/* --- Focus that survives on a dark ground ---------------------------------
   :focus-visible painted --blue (#024D68) on every ground, including the five
   blue ones. On .closer (#012C3C) that outline is invisible: every control in
   the closing CTA, the hero, the utility bar and the lightbox had no usable
   keyboard indicator. Amber is the site's existing "active" colour and clears
   4.5:1 against all three dark grounds. */
.utility :focus-visible,.hero :focus-visible,.section--blue :focus-visible,
.section--dark :focus-visible,.section--deep :focus-visible,.closer :focus-visible,
.router:not(.section--accent) :focus-visible,.lightbox :focus-visible,
.statement--blue :focus-visible,.versus__col--good :focus-visible,
.site-header--overlay:not([data-stuck="true"]) :focus-visible{outline-color:var(--accent)}
/* Fields suppressed the outline and replaced it with a 16%-alpha ring, which
   is under the 3:1 a focus indicator needs. Ring strengthened and a real
   outline restored for keyboard focus only. */
.input:focus,.select:focus,.textarea:focus,.searchbar input:focus{
  border-color:var(--brand-600);box-shadow:0 0 0 3px rgba(3,125,170,.30)}
.input:focus-visible,.select:focus-visible,.textarea:focus-visible,
.searchbar input:focus-visible{outline:2px solid var(--blue);outline-offset:1px}

/* --- Grounds set background-color, never the shorthand --------------------
   Four surfaces still used the shorthand: body, .utility, .site-header and
   .site-footer. None of them carries a glow today, so nothing was visibly
   broken, but the rule exists precisely so that adding one later does not
   silently fail. Restated so the ground and its light stay separable. */
body{background-color:var(--surface)}
.utility{background-color:var(--dark)}
.site-header{background-color:var(--surface-raised)}
.site-footer{background-color:var(--surface-raised)}

/* --- Mobile: the hero pushed its own CTA off the screen -------------------
   At 390 the trust rail, the h1 and the lead ran past 1000px before the first
   button, on an 844px viewport. The rail keeps all three claims; it just
   stops spending 76px per row on them. */
@media (max-width:900px){
  .hero__body{padding:104px var(--gutter) 32px}
  .hero__brands{padding-top:44px}
}
@media (max-width:760px){
  .hero__trust{gap:9px;margin-bottom:22px}
  .hero__trust-item{padding-top:9px}
}
/* Eleven links in one 390px column ran to ~750px of footer. Two columns is
   what every width above 420 already does. */
@media (max-width:420px){
  .site-footer nav ul{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px 16px}
}
/* ==== end: system ==== */
/* ==== added: konkurs ==== */
/* --- .ledger — what survives a bankruptcy, commitment by commitment --------
   Used once, on konkurs.html. It is the one object on that page a sceptic
   actually reads, so it is a real <table> rather than a stack of divs: a
   header row, a row per commitment, three columns (what / who stands behind
   it / what happens when the installer is gone). Svea Solar owns the same
   object and buries it inside an FAQ accordion roughly 5 900px down; here it
   is the section.

   Rules borrowed rather than restated: type sizes come from the tokens, the
   hairlines are --brand-100 because the table sits on .section--tint, where
   plain --hair is nearly invisible.
   ------------------------------------------------------------------------ */
.ledger{width:100%;border-collapse:collapse;text-align:left}
.ledger th,.ledger td{vertical-align:top;padding:20px 32px 20px 0}
.ledger th:last-child,.ledger td:last-child{padding-right:0}
.ledger thead th{
  padding:0 32px 12px 0;
  font-family:var(--sans);font-size:var(--fs-mono);font-weight:400;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-faint);
  border-bottom:1px solid var(--brand);
}
.ledger tbody th{
  width:26%;font-size:17px;font-weight:600;line-height:1.35;
  letter-spacing:-.01em;color:var(--ink);
}
.ledger tbody td{font-size:16px;line-height:1.6;color:var(--ink-muted);text-wrap:pretty}
.ledger tbody td:first-of-type{width:24%}
.ledger tbody b{color:var(--ink);font-weight:600}
.ledger tbody th,.ledger tbody td{border-bottom:1px solid var(--brand-100)}
/* The source note below the table draws its own top rule, so the last row
   must not draw a second one 28px above it. */
.ledger tbody tr:last-child>*{border-bottom:0}

/* Below this the third cell is a sentence competing with two others for a
   phone's width, so the table linearises and every cell prints its own column
   heading from data-label. The <thead> is hidden from sight but stays in the
   accessibility tree, so a screen reader still reads the real header cells.
   Note the explicit :first-of-type reset: the desktop width:24% outranks a
   plain `.ledger tbody td` here, and left in place it wrapped the middle cell
   two words to a line on a phone. */
@media (max-width:820px){
  .ledger,.ledger tbody,.ledger tbody tr,.ledger tbody th,.ledger tbody td{display:block;width:auto}
  .ledger tbody td:first-of-type{width:auto}
  .ledger thead{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
  }
  .ledger tbody tr{padding:24px 0;border-bottom:1px solid var(--brand-100)}
  .ledger tbody tr:last-child{border-bottom:0;padding-bottom:4px}
  .ledger tbody th{padding:0 0 14px;border:0;font-size:19px}
  .ledger tbody td{padding:0;border:0}
  .ledger tbody td+td{margin-top:14px}
  .ledger tbody td::before{
    content:attr(data-label);display:block;margin-bottom:3px;
    font-family:var(--sans);font-size:var(--fs-mono-sm);letter-spacing:.08em;
    text-transform:uppercase;color:var(--ink-faint);
  }
}

/* The source line under the table. Not .note: it carries links, and .note's
   faint grey drops link contrast below the rest of the page's body copy. */
.ledger-src{margin-top:28px;padding-top:20px;border-top:1px solid var(--brand-100);
  max-width:78ch;font-size:15px;line-height:1.65;color:var(--ink-muted);text-wrap:pretty}
/* ==== end: konkurs ==== */
/* ==== added: deploy ==== */
/* --- 404: routes out ------------------------------------------------------
   Six .tile cells whose copy runs to two or three lines, so the "→" link
   would otherwise sit at a different height in every cell of a row. Pin it to
   the bottom of its own cell instead. .cols is a grid and already stretches
   its items, so height:100% resolves to the row height; on one column the
   rows are content-height and margin-top:auto is a no-op. */
.routes .tile{display:flex;flex-direction:column;height:100%}
.routes__go{margin-top:auto;padding-top:16px}
/* ==== end: deploy ==== */
/* ==== added: assets ==== */
/* --------------------------------------------------------------------------
   Image weight: the Mafi mask
   `.supplier__mark--mafi` masks with logo-mafi.png, a 587px file for an 82px
   box, and a mask reads the whole file however small the box is. The 240px
   copy beside it covers the 82px slot at 2x with headroom and costs 7.8 KB
   instead of 22 KB. It stays a PNG on purpose: `mask-image` with a WebP is
   fine in current browsers but not in Safari 15, which the rest of the sheet
   still supports.
   -------------------------------------------------------------------------- */
.supplier__mark--mafi{
  -webkit-mask-image:url("../assets/img/logo-mafi-240.png");
  mask-image:url("../assets/img/logo-mafi-240.png");
}
/* ==== end: assets ==== */
/* ==== added: jsfix ==== */
/* Nav overflow menu without JavaScript.
   The "Mer" button is inert with scripting off, and five nav destinations
   (batteri, artiklar, var finns vi, om oss, kontakt) are then reachable only
   from the footer, because .site-header__panel is display:none above 1100px.
   Hover and focus-within open the same panel with no script at all, so the
   header keeps its full reach. Gated on html:not(.js) so it never competes
   with the scripted version. */
html:not(.js) .site-nav li:hover > .nav-menu,
html:not(.js) .site-nav li:focus-within > .nav-menu{
  opacity:1;visibility:visible;transform:none;
}
/* ==== end: jsfix ==== */
/* ==== added: omoss ==== */
/* ==========================================================================
   om-oss + var-finns-vi
   These two pages carry the one thing three of the four benchmark sites do not
   have at all: named people, a phone number and a street address. 1KOMMA5°,
   Otovo and Greenely publish no phone number anywhere on their main pages. So
   the job here is not decoration; it is making the human material read as a
   record rather than as a brochure.
   ========================================================================== */

/* --- The fold: a record rail beside the lead -------------------------------
   The page head was 404px of flat grey carrying an eyebrow, a two-line H1 and
   two lines of lead, with the H1 capped at 22ch so it wrapped at ~710px and
   left roughly 470px of empty ground to its right. Nothing on that screenful
   could be checked or acted on. Otovo's fold states three promises above its
   H1; Svea's carries a linked review score. This rail carries three facts that
   are already published in the footer of every page and can be verified in a
   public register, on a map and by dialling. It bottom-aligns with the lead, so
   the two columns resolve on one edge instead of leaving a hole. */
.page-head--record{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(216px,258px);
  gap:0 clamp(40px,4.6vw,72px);
  align-items:end;
  padding-top:clamp(44px,calc(33.5px + 2.66vw),65px);
  padding-bottom:clamp(34px,calc(25.6px + 2.15vw),51px);
}
.page-head--record .page-head__text{min-width:0}
.page-head--record .page-head__text p:last-child{margin-bottom:0}

.head-rail{margin:0;border-top:1px solid var(--hair-strong)}
.head-rail>div{padding:11px 0 12px;border-bottom:1px solid var(--hair)}
.head-rail dt{
  font-family:var(--sans);font-size:var(--fs-mono-sm);font-weight:500;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-faint);
}
.head-rail dd{
  margin:3px 0 0;font-family:var(--fig);font-size:14px;line-height:1.4;
  font-variant-numeric:tabular-nums;color:var(--ink);
}
/* Two of the three values are links, and a 19px-tall target is under the WCAG
   2.5.8 floor for a standalone control. The padding takes them to 25px without
   moving the underline off the word. */
.head-rail a{display:inline-block;padding:3px 0;color:inherit;text-decoration:none;
  border-bottom:1px solid var(--hair-strong);transition:color .2s ease,border-color .2s ease}
.head-rail a:hover{color:var(--brand);border-bottom-color:var(--brand)}

@media (max-width:900px){
  .page-head--record{grid-template-columns:minmax(0,1fr);gap:30px 0;align-items:start}
  .head-rail{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0 24px}
  .head-rail>div{border-bottom:0;padding:12px 0 0}
}
@media (max-width:560px){
  .head-rail{grid-template-columns:minmax(0,1fr);gap:0}
  .head-rail>div{border-bottom:1px solid var(--hair);padding:10px 0 11px}
}

/* --- A heading and its link, on a phone -----------------------------------
   .head-row is a flex row: at 390 it left a 119px link on the same line as a
   129px heading, right edge flush to the gutter, 24px tall, which is exactly
   the WCAG 2.5.8 floor and reads as a link pushed into whatever room was left.
   Own line, own tap target. Scoped to a modifier so no other page moves. */
@media (max-width:640px){
  .head-row--stack{flex-direction:column;align-items:flex-start;gap:0}
  .head-row--stack>.link-arrow{padding-top:22px;padding-bottom:3px}
}

/* --- The team, as a directory ---------------------------------------------
   Was: three columns of avatar-over-name-over-paragraph. Two problems. The
   source photographs are 200–240px square files, so 104px is already their
   ceiling on a 2× screen and no amount of layout will make them the hero of
   the page; and the people without a bio left the last row ragged in a way
   that read as broken markup rather than as an honest gap.

   Now: two columns, each person a row with the portrait and the name on one
   baseline and the description beneath at full cell width. Four seats, all
   full: the two colleagues without a portrait or a sentence were measured at
   145px against 214px for the rest, so they are named in a line under the grid
   instead of holding two visibly empty cells in it. A hairline above each entry
   gives the grid the architecture the floating circles never had, and it is the
   same device .lan already uses further down the site. */
.team{
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:0 clamp(24px,3.2vw,48px);
}
.person{
  display:grid;
  grid-template-columns:auto minmax(0,1fr);
  align-items:center;
  /* The grid box stretches to its row, so auto rows would stretch with it and
     a person with a two-line bio would sit their portrait 10px lower than the
     one beside them. Content packs to the top instead; slack falls at the end. */
  align-content:start;
  column-gap:18px;row-gap:12px;
  padding:22px 0 26px;
  border-top:1px solid var(--hair);
}
.person__photo{grid-column:1;grid-row:1;width:96px;height:96px}
.person__id{grid-column:2;grid-row:1;min-width:0}
.person__bio{grid-column:1 / -1;grid-row:2;margin-top:0;max-width:52ch}

/* --- Porträtten i färg -----------------------------------------------------
   Här låg en hållning: de fyra porträtten är fyra skilda fotografier — ett
   taget i en bil med bilbältet och nackstödet innanför beskärningen, ett
   inomhus mot en dörrpost, ett utomhus mot en gång och en häck, ett som läser
   som ett porträtt. Tre ljusförhållanden och tre bakgrunder i samma rutnät såg
   improviserat ut, så de drogs till ett gemensamt gråregister med en märkeston
   över. Det var uttalat ett provisorium i väntan på en gemensam fotografering.

   JULIUS BESKED 3 AUGUSTI 2026: porträtten ska vara i FÄRG. Gråskalan och
   märketonen är därför borta, och det som gjorde dem befogade är kvar: de fyra
   bilderna skiljer sig nu synligt i ljus och bakgrund, vilket är priset för
   färgen. Ringen står kvar — den är en kant, inte en färgbehandling — och den
   verkliga lösningen är fortfarande en fotografering med en bakgrund. Läggs en
   utjämning tillbaka ska det vara på hela uppsättningen, aldrig på enstaka
   porträtt: fyra bilder där två är dämpade är sämre än fyra som skiljer sig. */
.team .person__photo{box-shadow:inset 0 0 0 1px var(--brand-100)}

@media (max-width:720px){
  .team{grid-template-columns:minmax(0,1fr)}
}
@media (max-width:520px){
  .person{column-gap:14px;padding:20px 0 22px}
  .person__photo{width:78px;height:78px;font-size:19px}
  /* The base sheet's `.person p{display:none}` below 520 also caught
     .person__role, so on a phone every name lost its job title as well as its
     description. Both come back: on a page whose whole argument is "here is
     who you will be talking to", the role and the sentence are the substance,
     not the trimming. The portrait gives up the width instead. */
  .person p.person__role,.person p.person__bio{display:block}
}

/* The two colleagues who are named but not yet photographed. Ruled like the
   entries above so it belongs to the directory rather than floating under it. */

/* The strongest line on the page, and on a phone the only thing on the section
   that dials. Inline at desktop; a full-width primary action below 640, because
   a 19px inline link is under the WCAG 2.5.8 target floor on the one device
   that can use it. */
.team__call{
  margin:26px 0 0;
  font-family:var(--sans);font-size:16px;line-height:1.6;color:var(--ink-muted);
}
.team__call a{
  color:var(--brand-600);font-weight:600;text-decoration:none;
  border-bottom:1px solid var(--brand-100);
  transition:color .2s ease,border-color .2s ease;
}
.team__call a:hover{color:var(--brand);border-bottom-color:var(--brand)}
.team__call__note::before{content:"·\00a0";color:var(--ink-faint)}
@media (max-width:640px){
  .team__call{display:grid;gap:12px;margin-top:30px}
  .team__call a{
    display:flex;align-items:center;justify-content:center;
    min-height:52px;padding:14px 22px;
    border:1px solid transparent;border-bottom-color:transparent;
    border-radius:var(--radius-sm);
    background-color:var(--brand);color:#fff;
    font-size:17px;font-weight:500;letter-spacing:-.01em;
  }
  .team__call a:hover{background-color:var(--brand-600);color:#fff;border-bottom-color:transparent}
  .team__call__note{font-size:14px;color:var(--ink-faint);text-align:center}
  .team__call__note::before{content:none}
}

/* --- Claim cards ----------------------------------------------------------
   Each of the three now ends in a link to the page that holds its receipt.
   Grid items stretch, so pushing the link with margin-top:auto lands all three
   on one baseline whatever the copy above it does. */
.card--claim .link-arrow{margin-top:auto;align-self:flex-start;padding-top:10px}

/* --- The company record on the blue band ----------------------------------
   .kv was built for figures on a light ground. Scoped to .kv--record rather
   than to .section--blue .kv so this cannot reach a future key/value list that
   someone else puts on a dark band. Labels stay a step back from values so the
   pair still reads as label/fact; the placeholder rows carry sentences rather
   than figures, so they opt out of the nowrap the numeric rows need. */
.kv--record{margin-top:0;border-top-color:var(--dark-hair)}
.kv--record>div{border-bottom-color:var(--dark-hair)}
/* Dubbelklassen är avsiktlig. Basens .kv dt/dd ligger senare i filen och
   vann på ordning, så registret stod i mörk skrift på det blå bandet efter
   sammanslagningen. Med .kv.kv--record slår specificiteten ordningen, och
   raderna är vita oavsett var basblocket råkar ligga. */
.kv.kv--record dt{color:var(--dark-body)}
.kv.kv--record dd{color:#fff}
.kv--record>div:hover{background:rgba(255,255,255,.05)}
/* .kv__open bar de fyra bolagsraderna som stod öppna (registreringsår, säte,
   behörighet, ansvarsförsäkring). Raderna togs bort 1 augusti 2026, och regeln
   med dem. */

/* The band the whole page rests on had five different starting lines: the
   eyebrow spanning the full container, the register's first row 46px below it,
   the H2 56px below that, and the two columns ending 63px apart because the
   split was vertically centred by the design-system pass. It now resolves to
   two: the H2's first line sits on the first <dt>, and the button row is pushed
   to the foot of its column so both columns end on the same edge. The one band
   on the site that has to read as a document should not stagger. */
.split__check{
  margin:24px 0 0;font-family:var(--sans);font-size:15px;line-height:1.6;
  color:var(--dark-body);
}
.split--record .split__lead .btn-row{margin-top:20px}
@media (min-width:981px){
  .split--record{
    align-items:stretch;
    grid-template-columns:minmax(0,.94fr) minmax(0,1.06fr);
    gap:clamp(48px,5.6vw,80px);
  }
  .split--record .split__lead{display:flex;flex-direction:column}
  .split--record .split__lead .split__check{margin-top:auto;padding-top:36px}
  /* Lifts the register so its first label sits on the H2's first line, past the
     eyebrow that now starts the left column. Optical, measured at 1440. */
  .split--record .kv--record{margin-top:26px}
}

/* Two shrink-wrapped buttons stacked on a ragged right edge (142px over 135px
   in a 350px column) is the cheapest amateur tell there is. Both fill the
   column below 640 and share both edges. */
@media (max-width:640px){
  .btn-row--stack{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}
  .btn-row--stack .btn{width:100%}
}

/* --- var-finns-vi: the map embed ------------------------------------------
   The embed is loaded with ?compact, which drops the map page's own 384px side
   panel. That panel listed the same 37 kommuner as the HTML below it and had to
   scroll internally to do it; at the height the embed could give it the list
   was cut off mid-row and read as a broken widget. Without it the map is just a
   map, and it gets the frame to itself.

   The frame is square because the map is. karta-malardalen.html draws into a
   700x700 viewBox with preserveAspectRatio="xMidYMid meet", so in a 16:9 frame
   the drawing scales to the height and centres: at 1084px wide that left 237px
   of dead ground on each side, the greyed-out rest of Sweden bleeding into one
   and the Baltic into the other. A square frame is the viewBox, so the drawing
   fills it edge to edge. Capped and centred so it never becomes taller than a
   screenful, and kept clear of the map page's own 760px breakpoint. */
.map-embed--square{max-width:800px;margin-inline:auto}
.map-embed iframe{height:auto;aspect-ratio:1 / 1}
.map-embed__note{max-width:800px;margin:14px auto 0;text-align:center}
@media (max-width:560px){
  .map-embed__note{text-align:left}
}

/* --- var-finns-vi: the kommun list ----------------------------------------
   Stockholms län is 20 of the 37. In a two-up grid it sat beside the four-name
   Uppsala column and left eight rows of hole; here it takes the full width and
   the other three share a row, so the layout states the distribution instead
   of fighting it. */
.lan-grid{
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:44px 32px;
}
.lan-grid .lan--wide{grid-column:1 / -1}
.lan-grid .lan ul{grid-template-columns:minmax(0,1fr)}
.lan-grid .lan--wide ul{grid-template-columns:repeat(4,minmax(0,1fr));gap:0 32px}
@media (max-width:860px){
  .lan-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:36px 28px}
  .lan-grid .lan--wide ul{grid-template-columns:repeat(2,minmax(0,1fr));gap:0 28px}
}
@media (max-width:560px){
  .lan-grid{grid-template-columns:minmax(0,1fr);gap:32px}
  /* 37 names one under another is 1500px of scroll on a phone, for a list
     whose longest entry ("Hallstahammar") measures under half the column. */
  .lan-grid .lan ul,.lan-grid .lan--wide ul{grid-template-columns:repeat(2,minmax(0,1fr));gap:0 18px}
}
/* ==== end: omoss ==== */
/* ==== added: varfor ==== */
/* ==========================================================================
   varfor-oss.html — the argument page
   Owner key: varfor. Five components live only on this page (.page-head--media,
   .skal, .analys, .omdomen, .garanti), so everything below is scoped to them
   or to .page-varfor on <main>, except the two corrections marked BUG, which
   fix rules that were already wrong.
   ========================================================================== */

/* --- The page head, with a photograph in it -------------------------------
   It opened on a 520px band of pale grey: an H1, a lede, two buttons and about
   460px of nothing to the right of where the heading wrapped. Svea, Otovo,
   1KOMMA5° and Greenely all open on an image, and blind, side by side, the
   difference in perceived spend was not close.
   Two columns inside the normal measure, photo on the right bleeding to the
   viewport edge with the same calc() .router__media uses on index.html: the
   distance from the container's content edge to the screen is half the
   leftover width plus one gutter, and it collapses to the gutter on its own
   below --measure. .page-head--media clips it, so the scrollbar's share of
   100vw never becomes a horizontal scroll. */
.page-head--media{max-width:none;margin:0;padding:0;overflow:hidden}
.page-head__grid{
  display:grid;grid-template-columns:minmax(0,1.04fr) minmax(0,.96fr);
  align-items:center;column-gap:clamp(36px,4.2vw,68px);
  padding-top:var(--pad-md);padding-bottom:clamp(30px,3.2vw,44px);
}
/* The measure is the column, not a --ch cap: a heading that breaks over three
   lines at ~500px is where the scale in this design comes from. */
.page-head--media h1{margin-bottom:22px;text-wrap:balance}
.page-head__media{
  margin:0;overflow:hidden;
  aspect-ratio:4/3;
  border-radius:var(--radius) 0 0 var(--radius);
  background-color:var(--surface-sunken);
}
.page-head__media img{width:100%;height:100%;object-fit:cover;object-position:center 46%}
@media (min-width:901px){
  .page-head__media{
    margin-right:calc(-1 * (max((100vw - var(--measure)) / 2, 0px) + var(--gutter)));
  }
}

/* --- The trust rail -------------------------------------------------------
   The rating sat in the 12px utility strip and nowhere else above the fold. Svea
   repeats its Trustpilot block directly under the hero CTA; Otovo runs a
   three-item rail of two-line stacks that costs ~50px and pre-frames the
   headline. This is that rail, across the full measure so the three facts get
   room to be specific rather than clipped into the text column. */
.page-head__trust{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:0 clamp(20px,2.4vw,34px);
  padding-bottom:var(--pad-sm);
}
.page-head__trust > li{
  padding-left:clamp(20px,2.4vw,34px);
  border-left:1px solid var(--hair);
}
.page-head__trust > li:first-child{padding-left:0;border-left:0}
.page-head__trust-item{display:flex;flex-direction:column;gap:4px;text-decoration:none}
.page-head__trust-item b{
  display:flex;align-items:center;gap:9px;
  font-family:var(--sans);font-size:16px;font-weight:600;
  letter-spacing:-.005em;color:var(--ink);
}
.page-head__trust-item > span{
  font-size:14px;line-height:1.45;color:var(--ink-muted);text-wrap:pretty;
}
/* Only the rating goes anywhere, so only the rating gets a hover. */
a.page-head__trust-item:hover b{text-decoration:underline;text-underline-offset:3px}
a.page-head__trust-item:focus-visible{outline-offset:6px;border-radius:var(--radius-sm)}

@media (max-width:900px){
  .page-head__grid{
    grid-template-columns:minmax(0,1fr);
    row-gap:clamp(26px,5vw,38px);
    padding-bottom:clamp(24px,4vw,34px);
  }
  /* Full-bleed on a phone, which is how all four benchmark sites open. */
  .page-head__media{
    order:1;aspect-ratio:16/10;border-radius:0;
    margin-left:calc(-1 * var(--gutter));margin-right:calc(-1 * var(--gutter));
  }
}
@media (max-width:760px){
  .page-head__trust{grid-template-columns:minmax(0,1fr);gap:12px}
  .page-head__trust > li{
    padding-left:0;padding-top:12px;
    border-left:0;border-top:1px solid var(--hair);
  }
  .page-head__trust > li:first-child{padding-top:0;border-top:0}
}

/* --- Mid-page exits big enough to hit --------------------------------------
   The two .link-arrow links out of this page rendered 24px tall at 390, well
   under the 44px minimum, and they are the only exits between the head and the
   closer. Scoped to this page so no other owner's layout moves. */
@media (max-width:640px){
  .page-varfor .link-arrow{
    min-height:44px;padding-top:10px;padding-bottom:12px;
    /* The rule is the component's border-bottom, so padding for a tap target
       would drag the line 20px away from the words. Underline the text itself
       instead and let the padding be invisible. */
    border-bottom:0;
    text-decoration:underline;text-decoration-thickness:1px;
    text-underline-offset:6px;text-decoration-color:var(--hair-strong);
  }
  .page-varfor .section--dark .link-arrow,
  .page-varfor .section--blue .link-arrow{text-decoration-color:rgba(234,243,247,.42)}
}

/* --- BUG: the Analysen stage letterboxed three of its four photos ---------
   `.analys__frame img:not(.analys__blur):last-child{object-fit:contain}` was
   meant for the one portrait shot that filled the 4:3 stage with a blurred
   copy of itself. But :last-child is true of the ONLY img in the other three
   frames too, so the takyta (16:9), elbil (2:1) and anläggning (1:1) photos
   all rendered inside flat grey bars. The markup proves the intent: those
   three carry `style="object-position:center 58%"`, a no-op under contain.
   The portrait shot and its blurred backdrop are gone as of this pass, so all
   four frames now cover and the rule has no exception left to make. The dead
   `.analys__frame img.analys__blur` block in the base sheet belongs to whoever
   owns css/site.css; it matches nothing on the site now. */
.analys__frame img:not(.analys__blur):last-child{object-fit:cover}

/* --- Analysen: the rows have to look like rows you can press ---------------
   The only signal that the list was interactive was an accent bar on the row
   that was already selected, so on a phone the first press appeared to do
   nothing. Four hairline segments with one lit reads as a rail with a position
   in it, which is how the README says to order a sequence: with layout. */
.analys__row::before{background-color:var(--hair);transform:scaleY(1)}
.analys__row[aria-selected="true"]::before{background-color:var(--accent)}
@media (prefers-reduced-motion:no-preference){
  .analys__row::before{transition:background-color .2s ease}
}
@media (hover:hover){
  .analys__row:hover::before{background-color:var(--hair-strong)}
}
/* At 390 the widget stood 787px tall, so pressing the fourth row changed a
   photo that was already off the top of the screen. Tighter rows and a 16:9
   stage bring the whole thing to about 660px, inside one screenful, so the
   swap always happens in view. */
@media (max-width:640px){
  .analys{gap:20px}
  .analys__stage{aspect-ratio:16/9}
  .analys__row{padding:14px 0 14px 18px}
  .analys__row::before{top:13px;bottom:13px}
  .analys__title{font-size:18px}
  .analys__desc{font-size:14.5px}
}

/* --- BUG: an empty star read as a filled one on any dark ground -----------
   `.stars svg.is-empty{fill:var(--hair)}` is #DCE5E8. Against --brand-800 that
   is brighter than the amber it is supposed to be missing from, so the one
   four-star review in the wall showed as five. The omdömen band is the first
   place on the site .stars has sat on a dark ground. */
.section--dark .stars svg.is-empty,.section--deep .stars svg.is-empty,
.section--blue .stars svg.is-empty{fill:rgba(255,255,255,.24)}

/* --- Inline links in prose ------------------------------------------------
   The site had no way to mark a link inside a sentence: `a{color:inherit}`
   made one invisible until hover. Several sentences on this page need to lead
   somewhere (garantitiden, garantitabellen, konkursartikeln, the phone number)
   and none of them is a .link-arrow, which is a block-level ask. Underline in
   the blue family, offset so it never touches a descender. */
.link-inline{
  color:var(--brand-600);
  text-decoration:underline;text-decoration-thickness:1px;
  text-underline-offset:3px;text-decoration-color:rgba(3,100,135,.42);
}
.link-inline:hover{color:var(--brand);text-decoration-color:currentColor}
.link-inline--dark{color:#fff;text-decoration-color:rgba(255,255,255,.5)}
.link-inline--dark:hover{color:#fff;text-decoration-color:#fff}

/* --- The short column in a .split -----------------------------------------
   Both two-column bands left ~450px of nothing under a lede that had stopped.
   The column is sticky above 900, so what sits at its foot travels with the
   reader rather than scrolling out of the band. */
.col-foot{margin-top:clamp(28px,3vw,40px)}
@media (max-width:900px){.col-foot{margin-top:20px}}

/* --- Sex skäl ------------------------------------------------------------
   Replaces the .accordion, whose six panels all shipped data-open="true": the
   only thing a trigger could do was hide a paragraph, and each row carried a
   "×" advertising it. Same hairline rhythm the accordion had, minus the
   controls. On the tint ground the rules take --brand-100, which is what
   .section--tint already gives every other bordered thing. */
/* Sex skäl tog 1482 px, 247 px per rad. Samma nedskalning som .specs fick
   1 augusti: rubriken är en radrubrik, inte en avsnittsrubrik, och vadderingen
   var satt som om varje skäl vore ett eget avsnitt. */
.skal{border-top:1px solid var(--brand-100)}
.skal > li{padding:15px 0;border-bottom:1px solid var(--brand-100)}
.skal h3{
  margin-bottom:5px;
  font-size:18px;font-weight:600;letter-spacing:-.018em;color:var(--ink);
}
.skal p{max-width:64ch;font-size:15.5px;line-height:1.55;color:var(--ink-muted);text-wrap:pretty}
/* Egen regel: p{margin:0} gäller sajten över, så två stycken i samma skäl
   satt ihop utan luft och lästes som ett enda block. */
.skal p + p{margin-top:10px}
/* A fact that is not part of the argument sentence: the analysis count, and
   the person the contact promise is about. Set a step down and apart so the
   figure is not wrapping mid-clause when it finally arrives. */
/* .skal--person och .skal__face är borttagna, och sedan även den enda
   .skal__fact på sajten: skälet om kontaktperson bar först ett porträtt, sedan
   bara namnet, och till sist ingetdera. Ingen enskild person ska nämnas där.
   Regeln nedan har därmed ingen användare. Den är kvar för att den är korrekt
   och kostar fem rader; ska en fristående faktarad tillbaka i listan är det
   markupen som ska skrivas, inte formen. */
.skal__fact{
  margin-top:8px;
  font-family:var(--sans);font-size:15px;line-height:1.5;color:var(--ink-faint);
}
.skal__fact b{color:var(--ink);font-weight:600}
/* The one claim on the page with a face on it. Everything else in the list is
   a paragraph; this row is a portrait beside one. */
@media (max-width:900px){
  .skal > li{padding:22px 0}
  .skal p{font-size:16px}
    }

/* --- Omdömen: eleven, all on the page ------------------------------------
   Was a carousel showing one review at a time out of eleven, on the object the
   site is most able to prove. Svea repeats its Trustpilot score at every
   decision point; Greenely runs a twelve-card wall and leaves the four-star
   entries in. Multi-column rather than a grid so eleven quotes of unequal
   length pack without a ragged bottom edge on every row, and no card chrome:
   a hairline over each is the same idiom .checklist uses two bands earlier.
   The eleven are ordered in the markup so the three columns resolve within
   ~80px of one another; see the note above the list. */
.omdomen{margin-top:36px;columns:3;column-gap:clamp(32px,3.4vw,52px)}
.omdomen > li{
  break-inside:avoid;-webkit-column-break-inside:avoid;
  padding:20px 0 26px;border-top:1px solid var(--dark-hair);
}
.omdomen .stars{margin-bottom:12px}
.omdome__quote{
  font-size:17px;line-height:1.55;letter-spacing:-.006em;
  color:var(--dark-fg);text-wrap:pretty;
}
.omdome__by{
  margin-top:12px;
  font-family:var(--sans);font-size:14px;font-weight:500;
  color:rgba(201,222,231,.72);
}
/* .omdomen__source bar raden "Läs dem i original på vår Google-profil", som
   togs bort 1 augusti 2026 med sin tomma länk. */
/* The wall used to end on one short link under column one, with roughly
   1080x160 of empty dark ground to its right. The foot spans the band instead:
   the exit at the left, and at the right the sentence that makes the average
   checkable against the eleven ratings above it. */
.omdomen__foot{
  display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;
  gap:16px 40px;margin-top:clamp(32px,3.4vw,48px);
  padding-top:clamp(20px,2vw,28px);border-top:1px solid var(--dark-hair);
}
.omdomen__foot p{
  max-width:52ch;
  font-family:var(--sans);font-size:14px;line-height:1.55;
  color:rgba(201,222,231,.72);text-align:right;text-wrap:pretty;
}
/* The white pill disappears into a white card and floats on a dark one. On
   --dark it becomes an outline, so the score reads as a stated fact rather
   than as a badge stuck onto the band. */
.rating-badge--dark{
  background:transparent;border-color:var(--dark-border);color:var(--dark-body);
}
.rating-badge--dark b{color:#fff}
.section--dark .link-arrow{border-color:rgba(234,243,247,.34);color:var(--dark-fg)}
.section--dark .link-arrow:hover{border-color:var(--dark-fg);color:#fff}
@media (max-width:1080px){.omdomen{columns:2}}
@media (max-width:700px){
  .omdomen{columns:1;margin-top:28px}
  .omdomen > li{padding:18px 0 22px}
  .omdome__quote{font-size:16px}
  .omdomen__foot{gap:16px}
  .omdomen__foot p{text-align:left;max-width:none}
}

/* --- Garantier -----------------------------------------------------------
   A real table, surfaced as its own section. Svea has the only comparable
   object in the benchmark set and buries it ~5 900px down inside an FAQ
   accordion answer; Otovo's page titled "Pris och garanti" carries a single
   bullet. Rows are the actual components of an installation; every duration is
   a marked placeholder, because a warranty length is exactly the kind of
   figure rule 1 exists to protect.
   Set as a table, not a grid, so it is a table to a screen reader and to the
   clipboard as well as to the eye. */
.garanti{margin-top:8px}
.garanti table{width:100%;border-collapse:collapse;text-align:left}
.garanti thead th{
  padding:0 24px 12px 0;border-bottom:1px solid var(--brand);
  font-family:var(--sans);font-size:13px;font-weight:500;
  letter-spacing:.06em;text-transform:uppercase;color:var(--ink-muted);
}
.garanti tbody tr{border-bottom:1px solid var(--brand-100)}
.garanti tbody th,.garanti tbody td{padding:18px 24px 18px 0;vertical-align:top}
.garanti tbody th{
  width:31%;
  font-size:17px;font-weight:600;letter-spacing:-.022em;color:var(--ink);
}
.garanti tbody td{font-size:16px;line-height:1.55;color:var(--ink-muted);text-wrap:pretty}
.garanti tbody td:last-child{padding-right:0}
/* Tvåspaltig sedan LÄNGD-kolumnen togs bort 1 augusti 2026: .garanti .ph och
   den fasta 29 %-breddningen av sista cellen behövdes bara för den. */
.garanti__status{
  margin:0 0 clamp(24px,2.6vw,34px);
  font-size:15px;line-height:1.7;color:var(--ink-muted);text-wrap:pretty;
}
.garanti__note{
  margin-top:28px;
  font-size:15px;line-height:1.7;color:var(--ink-muted);text-wrap:pretty;
}
/* Below 720 a three-column table either scrolls sideways or crushes the
   longest garantityp to two characters a line. Each row becomes a block, the
   component name spans it, and the two cells sit side by side underneath, so a
   row is three lines rather than five and the section loses ~300px of scroll. */
@media (max-width:720px){
  .garanti thead{
    position:absolute;width:1px;height:1px;margin:-1px;padding:0;
    overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
  }
  .garanti tbody tr{
    display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);
    gap:4px 20px;padding:16px 0;
  }
  .garanti tbody th,.garanti tbody td,
  .garanti tbody td:last-child{width:auto;padding:0}
  .garanti tbody th{grid-column:1 / -1;margin-bottom:6px}
  .garanti tbody td::before{
    content:attr(data-label);
    display:block;margin-bottom:3px;
    font-family:var(--sans);font-size:12px;font-weight:500;
    letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);
  }
  .garanti tbody td{font-size:15px}
  .garanti__note{margin-top:24px}
}
/* ==== end: varfor ==== */
/* ==== added: batteri ==== */
/* ==========================================================================
   komplettera-med-batteri.html
   The retrofit page: the highest-intent segment on the site, and the one the
   amber router band on the start page funnels straight into.

   Everything below is scoped either to a class this page owns or to the
   .retro hook on its <main>, because the closing band, the header and the
   sprite come from partials/ and cannot be edited from here.
   ========================================================================== */

/* The hero photo bleeds past the 1180px measure, so the clip has to sit on a
   full-width ancestor: .page-head is itself only 1180 wide, and hiding its
   overflow cut the picture at x1310 instead of at the viewport edge. `clip`
   rather than `hidden` so it never becomes a scroll container.

   The page-head modifier here is .page-head--rehero, NOT .page-head--media:
   varfor-oss owns that second name for its own hero and its owner block lands
   later in this file, so sharing the name handed this page varfor's
   max-width:none and padding:0 and blew the gutter out at 390. A per-page
   modifier is never a shared name. */
.retro{overflow-x:clip}

/* --- Hero: a photograph above the fold ------------------------------------
   The head was text-only on --surface. At 1440 .page-head was 1180 wide, the
   H1 ran to 835 and the right ~400px was empty pale grey: no image, no proof,
   no figure. All four benchmark sites put either full-bleed photography
   (Otovo, Svea, 1KOMMA5°) or a rating with a review count inside the hero.

   The photo is the drone shot that used to render 494×278 in a corner of the
   next section, so the page gains a hero image and loses a postcard in one
   move. It bleeds to the viewport edge with the identical arithmetic
   .router__media uses on index.html: half the leftover width plus one gutter,
   clamped at 0 below --measure.

   The rating is deliberately NOT also pulled up here. Photograph or figure,
   not both; the money question gets its own band at #pris. */
.rehero{display:grid;gap:32px;align-items:stretch}
.rehero__body{min-width:0}
.rehero__media{
  position:relative;margin:0;overflow:hidden;
  border-radius:var(--radius);background-color:var(--surface-sunken);
}
.rehero__media img{
  position:absolute;inset:0;display:block;
  width:100%;height:100%;object-fit:cover;transform:translateZ(0);
}
/* Sits directly under the buttons, where Otovo and Svea put their
   no-obligation line. Same promise the closing band makes, in the same
   words, so the page cannot contradict itself between top and bottom. */
.rehero__terms{margin-top:20px}
.note--prep{max-width:68ch;margin-top:12px;line-height:1.6}
@media (max-width:900px){
  .rehero__media{aspect-ratio:16/10}
}
@media (min-width:901px){
  .rehero{grid-template-columns:minmax(0,1.04fr) minmax(0,.96fr);gap:clamp(36px,3.6vw,56px)}
  .rehero__media{
    border-radius:var(--radius) 0 0 var(--radius);
    margin-right:calc(-1 * (max((100vw - var(--measure)) / 2, 0px) + var(--gutter)));
  }
  /* 67px of display type in a 537px column throws one word onto a fourth
     line. Two or three stacked lines is the target measure. */
  .rehero__body .h1{font-size:clamp(39px,3.9vw,56px)}
}

/* --- Hero buttons: one width, not two ------------------------------------
   At 390 the pair stacked at 173px and 182px, both left-aligned, so the right
   edges were ragged by 9px. The closing band on this same page already sizes
   its pair to the wider of the two; this is that fix, applied 5000px earlier.
   The reassurance line is a sibling rather than a third child of the row,
   which is what let the row size itself off the longest string instead of the
   widest button. */
@media (max-width:640px){
  .retro .page-head .btn-row{
    display:grid;grid-template-columns:minmax(0,max-content);
    justify-content:start;gap:12px;
  }
  .retro .page-head .btn-row .btn{width:100%;justify-content:center}
}

/* --- The amber glow on this page's dark grounds ---------------------------
   --glow-rgb 255,192,0 at alpha .22 over --brand mixes to olive: at 2x it
   reads as a colour-management fault rather than as light. It showed three
   times here (the blue band, the "Med batteri" card, the closer). The token
   is site-wide and not this page's to change, so the alpha is dropped on this
   page's instances only, through the .retro hook on <main>. The blue band
   asks for the same thing in markup, with data-glow="left faint". */
.retro .closer{--glow-a:.11}
/* The card is a small box, so the same gradient sits at a much higher
   concentration inside it than it does across a full-width band. */
.retro .versus__col--good{--glow-a:.09}

/* --- BUG: the versus pills rendered as full-width bars ---------------------
   .versus__col is a column flexbox at the default align-items:stretch, so
   .reason__tag — an inline-flex pill everywhere else on the site — stretched
   edge to edge inside both cards. At 1440 that made "Som det ser ut idag"
   read as a disabled text input sitting above the heading, which is the one
   thing a comparison card must not look like.
   Also fixes the identical pair on artiklar.html, which is not my file. */
.versus__col > .reason__tag{align-self:flex-start}

/* --- The versus pair needed a footnote, not a fifth bullet ----------------
   Two of the four "med batteri" rows previously claimed effekttoppskapning
   and a monthly frekvensstöd income. lonsamhet.html, produkter.html and
   js/kalkyl.js all qualify both heavily (effektavgift runs at 0 kr/kW in the
   model; FCR-D is excluded outright). Rather than hedge inside a bullet, the
   claims are gone and the qualification sits under the pair as one line with
   a route to the page that does the arithmetic.
   NOTE for whoever owns artiklar.html: that page already ships a
   <p class="versus__foot"> under its own versus pair with no rule to match.
   This is the rule. If you write your own, delete one of them rather than
   letting two owner blocks fight over the same selector. */
.versus__foot{
  max-width:78ch;margin-top:28px;
  font-size:15px;line-height:1.65;color:var(--ink-muted);text-wrap:pretty;
}
.versus__foot .link-arrow{margin-left:6px;font-size:15px}

/* --- Reservkraft: the columns resolve together ----------------------------
   .split--narrow was align-items:center with an eyebrow and a heading on the
   left (195px) against three paragraphs on the right (387px), so there were
   96px of empty deep teal above "Reservkraft och ö-drift" and 96px below
   "strömmen går." The band's strongest headline sat in a hole.

   Rather than only top-align it, the left column now ends in a photograph
   that takes the leftover height: the row stretches, the heading block is
   intrinsic, the figure is flex:1 1 0 so it claims what is left rather than
   contributing its own 519px of intrinsic image height. The min-height is
   what stops it collapsing into a letterbox once the heading and the prose
   are close in length; past that point the left column sets the row and the
   prose keeps a normal column of air beneath it.

   It is also the one thing that breaks what was 1582px of unbroken blue on a
   phone, together with the tinted panel below. */
.req__head{align-items:stretch}
.req__headcol{display:flex;flex-direction:column;min-width:0}
.req__media{
  position:relative;flex:1 1 0;min-height:clamp(200px,17vw,250px);
  margin:32px 0 0;overflow:hidden;
  border-radius:var(--radius);background-color:var(--brand-800);
}
.req__media img{
  position:absolute;inset:0;display:block;
  width:100%;height:100%;object-fit:cover;object-position:50% 30%;
  transform:translateZ(0);
}
@media (max-width:980px){
  /* Stacked, there is no leftover to fill, so the figure takes a crop of its
     own. Wider than the source's 3:4 so it reads as a break in the band
     rather than as a second hero. */
  .req__media{flex:none;aspect-ratio:3/2;min-height:0;margin-top:28px}
}

/* --- Reservkraft: three requirements on their own surface -----------------
   Same idiom as .spec on the case block — a rule, then a label, then the
   prose — so the band introduces no new card type. Three columns rather than
   .checklist's two, because there are three requirements and a 2+1 grid
   leaves a hole. No check icons: these are conditions to satisfy, not things
   included in the price, and a check mark would say the opposite.

   The panel is --brand-800 on --brand: a real step on the ladder, not a
   translucent film. It is what stops the band reading as one undifferentiated
   wall at 390, where it runs to nearly two screenfuls. */
.req{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0 48px;margin:0;list-style:none}
.req li{padding-top:24px;border-top:1px solid rgba(255,255,255,.22)}
.req h3{margin-bottom:10px;font-size:19px;font-weight:600;letter-spacing:-.02em;color:#fff}
.req p{font-size:16px;line-height:1.62;color:rgba(255,255,255,.82);text-wrap:pretty}
.req--panel{
  background-color:var(--brand-800);border-radius:var(--radius);
  padding:clamp(26px,2.8vw,40px) clamp(22px,2.4vw,36px) clamp(30px,3.2vw,44px);
  gap:0 clamp(28px,3vw,44px);
}
.req--panel li{border-top-color:rgba(255,255,255,.26)}

/* The band's closing line. It used to be a flex row with the link pushed to
   the far edge and bottom-aligned, which put 194px of empty blue between the
   note and the link and set the link's baseline against whichever line the
   placeholder box happened to end on. It read as a caption for the
   placeholder. Both now stack at the container gutter, the way
   .versus__foot already resolves its own route out. */
.req__foot{
  display:flex;flex-direction:column;align-items:flex-start;
  gap:18px;margin-top:40px;
}
.req__note{
  max-width:72ch;margin:0;
  font-size:15px;line-height:1.7;color:rgba(255,255,255,.78);text-wrap:pretty;
}
.req__foot .link-arrow{font-size:15px}
@media (max-width:860px){
  .req{grid-template-columns:minmax(0,1fr);gap:0}
  .req li{padding-top:20px}
  .req li + li{margin-top:24px}
  .req__foot{margin-top:32px}
}

/* --- Passar det min anläggning: stop decapitating the product ------------
   The source is 2:3 (1024×1536) and .shot--product is a 4:5 box with a
   bottom-anchored cover crop, so 121px came off the base and the battery
   stack was cut mid-module at both widths. The product the page sells was
   losing its feet.

   The fix is geometric rather than a new crop: the image column is narrower
   than 2:3 relative to the row height, so cover now takes its slack off the
   sides. The full stack is inside the frame at every width, and because the
   row stretches there is no void beside it either. object-position leans
   right of centre, which is where the battery is. */
.fitsplit{align-items:stretch}
.retro .shot--stack{aspect-ratio:4/5;height:100%}
.retro .shot--stack img{object-position:62% 50%}
@media (min-width:981px){
  .fitsplit{grid-template-columns:minmax(0,.74fr) minmax(0,1.26fr);gap:clamp(44px,4.6vw,72px)}
  .retro .shot--stack{aspect-ratio:auto;min-height:520px}
}

/* --- One quote, no card --------------------------------------------------
   Chrome-free the way Otovo and 1KOMMA5° set their content blocks: a rule, a
   quote at reading size, the stars and the name, and a route to the other
   ten. No panel, no border box, no shadow, no photo — nothing here pretends
   to be more verified than a Google review is.

   Two fixes. The route out used to sit at x1017, 288px right of a quote that
   ended at x729, bottom-aligned to the caption; it now follows the text it
   belongs to. And the attribution was set in IBM Plex Mono at 13px, which
   makes a customer read as a debug label rather than as a person. Svea and
   Greenely both set social proof in the body face at body size. The mono
   stays on .ph, where the code look is doing a job. */
.proof{
  margin:64px 0 0;padding-top:36px;border-top:1px solid var(--brand-100);
  display:flex;flex-direction:column;align-items:flex-start;gap:18px;
}
.proof blockquote{margin:0;max-width:54ch}
.proof blockquote p{
  font-size:clamp(19px,1.6vw,23px);line-height:1.5;letter-spacing:-.015em;
  color:var(--ink);text-wrap:pretty;
}
.proof figcaption{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px;
  font-family:var(--sans);font-size:15px;letter-spacing:normal;
  color:var(--ink-muted);
}
.proof .link-arrow{font-size:15px}
@media (max-width:860px){
  .proof{margin-top:48px;padding-top:28px;gap:16px}
}

/* --- #pris: the row the page was not on ----------------------------------
   The page carried no cost signal at all across 4728px. Reuses .pbands and
   .pband from index.html rather than inventing a second price card, so the
   two places on the site that answer the money question answer it in the same
   object. The axis differs because the question differs: on a retrofit the
   price turns on the inverter already on the wall, not on the size of the
   house.

   .pbands__hold says in words why the cards are empty; .pbands__foot carries
   the deduction arithmetic and the route to the model. They sit as two columns
   under the row rather than stacked, because two measured paragraphs on the
   left leave 500px of white on the right and that reads as a section that ran
   out rather than as a section that closed.

   An honestly marked gap keeps the row in the buyer's comparison sheet; an
   absent row deletes it, and an invented one is worse than either. */
.pbands__notes{display:grid;gap:20px 48px;margin-top:32px}
.pbands__hold{
  margin:0;
  font-size:15px;line-height:1.7;color:var(--ink-muted);text-wrap:pretty;
}
.pbands__notes .pbands__foot{max-width:none;margin:0}
@media (min-width:861px){
  .pbands__notes{grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:48px}
}
.retro .pband__kwh{margin-top:8px;line-height:1.5}
/* ==== end: batteri ==== */
/* ==== added: steg ==== */
/* ==========================================================================
   steg-for-steg.html — the process page
   ==========================================================================
   Everything is scoped to .page-steg on <body>. The page leans on shared
   components (.page-head, .closer, .link-arrow, .tile, .cols) and the
   corrections below belong to this page, not to the system.

   What was wrong, and which rule answers it:

   1. The fold was 404px of eyebrow, heading and deck on flat --surface: no
      photograph, no proof, no button, and the first CTA 83% down the page.
      .steg-head is a two-column fold with a bleeding photograph, a three-item
      fact rail and one text CTA. Every fact restates a sentence that already
      appears further down this page, so nothing new is claimed.
   2. The sticky media was a 464x348 frame inside a 494px column: 552px of bare
      ground against 348px of image on every screenful, and the largest picture
      on a 4 400px page. The frame now bleeds to the left viewport edge and is
      square, capped to the viewport height, so it fills about three quarters
      of the screen instead of a third.
   3. Two sentences carrying the page's boldest admissions were set at 15px,
      the smallest type on the page, under hairlines that stopped at half the
      grid width. Both are 18px now, in blocks that occupy their whole row.
   4. Five body sizes across four bands, and two sibling column heads that
      disagreed (19 vs 24). Now: 18px prose, 16px column lists, 19px every
      column head in both bands, 15px for captions and notes only.
   5. Below 900px the shared frame cannot work at all, because a sticky grid
      item can only travel inside its own grid area and in one column that area
      is the height of the frame. Each step carries its own .jstep__shot there
      and the frame is dropped.

   Two picture blocks (the fold and "Tre parter") pair a photograph with a
   column of type. In both the photograph takes whatever height the type needs
   rather than a fixed ratio, so the two halves resolve together and the void
   under the shorter column cannot come back. That costs one rule each:
   flex:1 with height:0, so the image contributes nothing to the row and then
   grows into it.

   Namespaces are .included / .parties rather than the obvious .incl, because
   index.html took .incl for its own "Det här ingår i priset" block in the same
   round of work, and that block sits later in the cascade.
   ========================================================================== */

/* Both bleeds below run to the viewport edge. 100vw counts the scrollbar and
   the content box does not, so the last few pixels have to be clipped
   somewhere full-width. It is overflow-x:clip and never overflow:hidden:
   hidden turns the element into a scroll container and position:sticky inside
   it silently stops working. */
.page-steg main{overflow-x:clip}

/* --- The fold ------------------------------------------------------------ */
.page-steg .page-head{
  display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);
  gap:56px;align-items:stretch;
}
/* --fs-h1 tops out at 67px, which broke this heading over three lines with a
   single nine-letter word alone in the middle. At 60px in a 538px column it
   sets as two even lines. The measure is fixed, not the words. */
.page-steg .page-head h1{font-size:clamp(39px,calc(24px + 2.5vw),60px);text-wrap:balance}
.page-steg .steg-head__col{
  display:flex;flex-direction:column;align-items:flex-start;justify-content:center;
}
.page-steg .steg-facts{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;
  width:100%;max-width:540px;margin-top:32px;padding-top:20px;
  border-top:1px solid var(--hair);
}
.page-steg .steg-facts li{display:flex;flex-direction:column;gap:2px}
.page-steg .steg-facts b{font-size:18px;font-weight:600;letter-spacing:-.01em}
.page-steg .steg-facts span{font-size:15px;color:var(--ink-faint)}
.page-steg .steg-head__act{
  display:flex;flex-direction:column;align-items:flex-start;gap:14px;margin-top:32px;
}

/* The bleed is the .router__media formula mirrored: half the leftover width
   plus one gutter. */
.page-steg .steg-head__shot{
  position:relative;
  margin-right:calc(-1 * (max((100vw - var(--measure)) / 2, 0px) + var(--gutter)));
  min-height:420px;overflow:hidden;background-color:var(--surface-sunken);
  border:1px solid var(--hair);border-right:0;
  border-radius:var(--radius) 0 0 var(--radius);
}
.page-steg .steg-head__shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}

/* --- The scroll journey --------------------------------------------------- */
@media (min-width:901px){
  .page-steg .journey__grid{grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);gap:64px}
  .page-steg .journey__media{
    grid-template-columns:minmax(0,1fr) auto;gap:28px;
    margin-left:calc(-1 * (max((100vw - var(--measure)) / 2, 0px) + var(--gutter)));
    /* The frame is square and its side is about half the viewport width, so
       centring it is 50vh minus half of that. Written out rather than tuned
       to one width, because a fixed offset only centres at one viewport. */
    top:clamp(24px,calc(50vh - 25vw + 30px),300px);
  }
  /* DOM order is rail then frame. Once the photograph runs off the left edge
     the rail belongs between it and the steps, so the two are placed rather
     than reordered in the markup. */
  .page-steg .journey__frame{
    grid-column:1;grid-row:1;
    aspect-ratio:1/1;max-height:calc(100vh - 48px);
    border-left:0;border-radius:0 var(--radius) var(--radius) 0;
  }
  .page-steg .journey__rail{grid-column:2;grid-row:1;min-height:0}
  .page-steg .journey__cap{padding:26px 24px 20px}
}

/* --- The per-step photo, phones only ------------------------------------- */
.jstep__shot{display:none}
@media (max-width:900px){
  .journey__media{display:none}
  .jstep__shot{
    display:block;margin-top:22px;overflow:hidden;
    border:1px solid var(--hair);border-radius:var(--radius);
    background-color:var(--surface-sunken);
  }
  /* 3:2 is the source ratio of all four files, so nothing is cropped */
  .jstep__shot img{display:block;width:100%;height:auto;aspect-ratio:3/2;object-fit:cover}
}

/* --- Vad som ingår -------------------------------------------------------
   Otovo's itemised block, borrowed as a method rather than a layout: four
   named groups, plain lines, no card, no icon, no rule between items. The
   hairline over each group is .tile, already in the system. */
.included .tile{border-top-color:rgba(255,255,255,.26)}
.included .tile:hover{border-top-color:rgba(255,255,255,.62)}
.included__list{display:flex;flex-direction:column;gap:10px;margin-top:14px}
.included__list li{font-size:16px;line-height:1.45;color:rgba(255,255,255,.84);text-wrap:pretty}

/* .included__close (the "Det som inte ingår" bar) was removed from
   steg-for-steg.html on comment, 1 August 2026, and it was the only user of
   these rules. Owner block kept empty so a re-run cannot resurrect them. */

/* --- Vem gör vad ---------------------------------------------------------- */
/* A percentage track, not 420px: a fixed one left the argument narrower than
   the picture at 901px, which is the wrong way round. */
.parties__intro{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,38%);
  gap:56px;align-items:stretch;margin-bottom:var(--pad-sm);
}
.parties__intro-body{
  display:flex;flex-direction:column;align-items:flex-start;justify-content:center;
}
.parties__intro-body .eyebrow{margin-bottom:16px}
.parties__intro-body h2{margin-bottom:16px}
.parties__lead{
  width:100%;margin-top:36px;padding-top:24px;border-top:1px solid var(--brand-100);
}
.parties__lead-title{
  font-size:19px;font-weight:600;letter-spacing:-.02em;line-height:1.25;margin-bottom:8px;
}
.parties__foot{font-size:18px;line-height:1.55;max-width:60ch;color:var(--ink-muted);text-wrap:pretty}

/* height:0 with flex:1 is what stops the picture driving the row: an image
   left at its intrinsic height would make this column 618px tall against a
   466px argument and put the void back, one column over from where it was. */
.parties__shot{display:flex;flex-direction:column;min-height:420px}
.parties__shot-media{
  display:block;position:relative;flex:1;min-height:340px;overflow:hidden;
  border:1px solid var(--hair-strong);border-radius:var(--radius);
  background-color:var(--surface-sunken);
}
.parties__shot-media img{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center 40%;
}
.parties__shot figcaption{
  margin-top:14px;font-size:15px;line-height:1.5;color:var(--ink-faint);text-wrap:pretty;
}
.parties .tile p{margin-top:10px}

/* --- The close ------------------------------------------------------------
   .closer h2 was --fs-h2-sm, byte identical to .jstep h3, so the page's close
   carried no more weight than its middle. */
.page-steg .closer h2{font-size:var(--fs-h2);line-height:1.08;letter-spacing:-.028em}

/* WCAG 2.2 AA wants 24x24 and .link-arrow measured 215x24, exactly at the
   floor. The target is grown with a pseudo-element so the underline stays
   where the type puts it rather than drifting away from the words. */
.page-steg .link-arrow{position:relative}
.page-steg .link-arrow::after{
  content:"";position:absolute;left:0;right:0;top:50%;height:44px;
  transform:translateY(-50%);
}

/* --- Phones and small tablets --------------------------------------------- */
@media (max-width:900px){
  .page-steg .page-head{grid-template-columns:minmax(0,1fr);gap:28px}
  .page-steg .steg-head__shot{
    margin-left:calc(var(--gutter) * -1);margin-right:calc(var(--gutter) * -1);
    min-height:0;aspect-ratio:3/2;border-left:0;border-radius:0;
  }
  .page-steg .steg-facts{max-width:none}
  .parties__intro{grid-template-columns:minmax(0,1fr);gap:32px;margin-bottom:40px}
  .parties__shot{min-height:0}
  .parties__shot-media{flex:none;min-height:0;aspect-ratio:4/5}
}
@media (max-width:560px){
  /* Three two-line stacks do not fit 390px, so the rail turns into rows:
     promise left, qualifier right, one hairline each. */
  .page-steg .steg-facts{grid-template-columns:minmax(0,1fr);gap:0}
  .page-steg .steg-facts li{
    flex-direction:row;justify-content:space-between;align-items:baseline;
    gap:16px;padding:11px 0;border-bottom:1px solid var(--hair);
  }
  .page-steg .steg-facts li:last-child{border-bottom:0;padding-bottom:0}
}
/* ==== end: steg ==== */
/* ==== added: galleri ==== */
/* ==========================================================================
   Galleri — rebuilt around the one job a gallery has
   Before: the first photograph started at y=694 in a 900px viewport at 1440
   and at y=751 in an 844px one at 390, so the first screen of a page called
   Galleri was an essay. Seven of eleven slides ended in a 515x320 hole where
   the row maths did not resolve, with the lone last photo 34px wider than the
   two above it. The document height swung 649px between slides, and the
   eleven 10x10px dots that were the only route to ten of the eleven houses
   moved with it.

   The shape is now fixed by construction, in tools/gallery.py: every slide is
   three photographs in the same three roles, two half-width above one full
   width. There is no leftover cell to fall into, and every slide is the same
   height, so nothing below the wall moves when a dot is pressed.
   ========================================================================== */

/* --- The head, cut to let a photograph into the first screen --------------
   Three sentences did one sentence's work. "Riktiga tak, fotograferade efter
   att vi lämnat dem" is the whole argument; the shot types are now the label
   under each photograph and the bläddra-instruction sits with the dots. */
.page-head--gal{padding-bottom:16px}
.page-head--gal h1{margin-bottom:18px}
.page-head--gal .lead{margin-bottom:0}

/* Otovo's trust rail above the H1, as one line. As three two-line stacks in a
   44px-gap flex it broke 2 + 1 at 390, with the third item starting back at
   x=20 under a 121px-wide neighbour and an empty cell beside it. A rail that
   wraps as a sentence cannot do that. */

/* --- The bar above the wall ----------------------------------------------
   The dots used to sit BELOW the photographs, so the control the reader had
   just pressed was the thing that jumped. They sit above the wall now, where
   a carousel's control belongs, and the note that explains why no house is
   named sits beside them at body size instead of as the page's only centred
   block, set 15px in --ink-faint under everything else. */
.gal-section{padding-top:56px}
/* .galproof och .galbar__hint är borttagna. Den första var
   taluppställningen i galleriets sidhuvud, den andra raden "Klicka på en
   bild för att se den stor". Ingen sida använder dem längre; se
   galleri.html och tools/gallery.py för skälen. */
.galbar{
  display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;
  gap:18px 40px;margin-bottom:22px;
}
.galbar__note{
  max-width:46ch;margin:0;
  font-size:16px;line-height:1.55;color:var(--ink-muted);text-wrap:pretty;
}
.galbar__nav{display:flex;flex-direction:column;align-items:flex-end;gap:9px}
/* Sans, not mono: mono is for labels and counters on this site, and a whole
   sentence set in it reads as a filename rather than as an instruction. */
.slidenav{margin-top:0;gap:6px}

/* WCAG 2.5.8 sets 24x24 as the floor and iOS guidance is 44. The dot was
   10x10 with a 21px pitch, and the selected one only looked bigger because of
   transform:scale(), which does not grow a hit box. The button is a real
   32x44 target now with the mark drawn by the span inside it, and the boxes
   abut, so there is no dead gap between two targets either. The selected mark
   is a bar rather than a scaled-up dot: it reads as position along a set. */
.dots{gap:0}
.dot{
  width:32px;height:44px;display:grid;place-items:center;
  background-color:transparent;border-radius:6px;transform:none;
}
.dot>span{
  display:block;width:10px;height:10px;border-radius:999px;
  background-color:var(--hair-strong);
  transition:background-color .2s ease,width .25s var(--ease-out);
}
.dot:hover{background-color:transparent}
.dot:hover>span{background-color:var(--ink-faint)}
.dot[aria-selected="true"]{background-color:transparent;transform:none}
.dot[aria-selected="true"]>span{width:26px;background-color:var(--brand)}
.dot:focus-visible{outline:2px solid var(--blue);outline-offset:-2px}
@media (prefers-reduced-motion:reduce){.dot>span{transition:none}}

/* --- The wall ------------------------------------------------------------
   A justified flex wall cannot resolve an odd count: whatever is left over
   sits alone with the rest of the row empty beside it, and the old cap made
   that leftover WIDER than the photographs above it. Two columns with the
   third figure spanning both is the same layout with the arithmetic removed.
   Every slide is 2 + 1, so the shape and the height are the same on all ten. */
.gal{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.gal__item{max-width:none;flex:none;min-width:0}
.gal__item--wide{grid-column:1 / -1}

/* css/site.css:827 wrote this scrim, this type and this transition for a
   caption that did not exist in the markup: 45 photographs, zero
   <figcaption>, and a :hover rule that could never fire. The caption exists
   now and it is the page's labelled triptych, the same three roles under
   every house, so the reader can see that the set is edited rather than
   dumped. It is also what the lightbox prints under the enlarged image. */
.gal__item figcaption{
  opacity:1;padding:36px 15px 11px;
  background-image:linear-gradient(transparent,rgba(6,32,42,.06) 35%,rgba(6,32,42,.74));
}

/* --- Lightbox ------------------------------------------------------------
   It opened a 1120x630 file inside a 1440x820 box and left 160px of dark on
   either side, after promising "klicka på en bild för att se den stor".
   object-fit lets the photograph fill the box it was given; the extra bottom
   padding is the bar's room, so the bar stops sitting on the image. Because
   data-gallery now sits on the wrapper rather than on each slide, the arrows
   walk all thirty photographs instead of stopping at the house you opened. */
.lightbox{padding-bottom:84px;background-color:rgba(6,26,34,.975)}
.lightbox img{width:100%;height:100%;object-fit:contain;border-radius:0}

/* --- The band the wall had earned ----------------------------------------
   Nothing stood between the photographs and the closing CTA, so the page had
   one shape: head, photos, ask. This is the only honest objection to a
   gallery, made before the reader makes it. */
.galwhat{
  display:grid;grid-template-columns:minmax(0,7fr) minmax(0,9fr);
  gap:clamp(32px,4.5vw,72px);align-items:start;
}
.galwhat__lead .eyebrow{margin-bottom:14px}
.galwhat__lead h2{margin-bottom:16px}
.galwhat__lead p{margin-bottom:24px;color:var(--ink-muted);text-wrap:pretty}
.galwhat__cols{display:grid;grid-template-columns:1fr 1fr;gap:clamp(24px,2.6vw,40px)}
.galwhat__col h3{
  margin:0 0 16px;padding-bottom:12px;border-bottom:1px solid var(--brand-100);
  font-size:16px;font-weight:600;letter-spacing:-.012em;color:var(--ink);
}
.galwhat__col ul{display:grid;gap:13px;margin:0;padding:0;list-style:none}
.galwhat__col li{
  position:relative;padding-left:19px;
  font-size:16px;line-height:1.5;color:var(--ink-muted);text-wrap:pretty;
}
.galwhat__col li::before{
  content:"";position:absolute;left:0;top:.66em;
  width:8px;height:1px;background-color:var(--brand);
}
/* .galwhat__ph bar raden om effekt, ort och år per anläggning, borttagen
   1 augusti 2026. */

@media (max-width:980px){
  .galwhat{grid-template-columns:1fr;gap:36px}
}
@media (max-width:700px){
  /* One column: two half-width photographs at 350px would be 165px wide. */
  .gal{grid-template-columns:1fr;gap:14px}
  /* Every pixel above the wall is a pixel of photograph pushed under an 844px
     fold, so the head's rhythm is tightened here rather than at 1440. */
    .page-head--gal{padding-bottom:10px}
  .gal-section{padding-top:34px}
  .galbar{flex-direction:column;align-items:stretch;gap:14px;margin-bottom:16px}
  .galbar__note{max-width:none}
  .galbar__nav{align-items:stretch;gap:10px}
  /* Left, like everything else on this page: the dots are justified edge to
     edge here, so a centred caption under them lines up with nothing. */
    /* Ten dots plus two arrows do not fit 350px at a 32px pitch, so the dots
     take the rest of the row and split it: still ~26px wide and 44 tall,
     which clears the 24x24 floor without dropping either arrow. */
  .slidenav{gap:8px}
  .slidenav__arrow{width:38px;height:38px;font-size:16px}
  .dots{flex:1 1 auto;flex-wrap:nowrap;justify-content:space-between}
  .dot{flex:1 1 0;width:auto;min-width:0}
  .galwhat__cols{grid-template-columns:1fr;gap:28px}
  /* A landscape photograph in a 390px window is width-limited whatever the
     padding is, so the side padding only ever costs pixels. */
  .lightbox{padding:0 0 76px}
}

/* --- Artiklar: the search field was collapsing to its own content ---------
   .searchbar sits in a .head-row (flex, space-between) with no basis, so it
   shrank to 217px inside a 1084px row and read as an afterthought beside the
   heading. It claims a real column now and stops growing past its measure. */
.searchbar{flex:1 1 300px;max-width:36ch;margin-left:auto}
.head-row .searchbar__count{margin-top:2px}
@media (max-width:720px){.searchbar{max-width:none;margin-left:0}}
/* ==== end: galleri ==== */
/* ==== added: lon-design ==== */
/* ==========================================================================
   lonsamhet.html — display figures, card weighting, one type ladder
   Owner key: lon-design. Everything here is a design pass on top of the
   structure that already exists; nothing addresses the model, and no figure is
   typed anywhere (js/kalkyl.js still fills every one of them).
   ========================================================================== */

/* --- 1. A display figure is money, not a terminal readout -----------------
   Every figure large enough to be a headline was set in IBM Plex Mono at
   48–67px with wide default tracking, which is the single thing that made
   these pages read as a spreadsheet: mono at display size is a data dump, not
   a price. None of Svea, Otovo, 1KOMMA5° or Greenely sets a headline figure in
   mono; Greenely's "0 kr/mån i 18 mån" is in its display serif.
   Display figures now take Familjen Grotesk at the same negative tracking the
   headings carry, and keep font-variant-numeric:tabular-nums so a value can be
   swapped by the calculator without the line reflowing under it.
   Mono stays exactly where it earns its keep: small values in an aligned
   column, where tabular width genuinely helps the eye compare rows — .kv dd,
   .rrow, .assump__val, .ebar__key b, and every caption-size
   count elsewhere on the site. The two roles are now separated rather than
   sharing one face. */
.savings__figure,
.compare__figure,
.total__fig,
.swing__fig,
.swing__sum-fig,
.reviewbar__figure b,
.prod__val,
.freq__value b{
  font-family:var(--sans);
  font-weight:600;
  letter-spacing:-.035em;
  font-variant-numeric:tabular-nums;
}
/* Grotesk digits are narrower than Plex Mono's fixed .6em advance, so the
   figures that were sized to fill their slot in mono are stepped up rather
   than left looking shrunken in the same box. */
.savings__figure{font-size:clamp(44px,calc(20px + 3.9vw),84px);line-height:.94}
.total__fig{font-size:clamp(38px,calc(24.7px + 1.69vw),64px)}
.total__fig small{font-family:var(--sans);font-size:16px;font-weight:400;
  letter-spacing:0;color:var(--ink-muted)}

/* --- 2. The two comparison cards were making opposite arguments at the same
   weight -------------------------------------------------------------------
   One column is the outcome the reader came for; the other is the status quo
   it is measured against. Identical width, identical card, identical figure
   size meant the page put them on a par and therefore made no argument at all.
   Worse, the baseline column was the only one with a fill (.card--flat paints
   --surface), so the loudest object in the block was the one nobody wants.
   The optimised column keeps the card and gains the fill, the width and a
   larger figure. The baseline column loses its chrome entirely — no
   background, no border, no radius — which is Otovo's rule (content cards
   carry no chrome; exactly one filled card exists in their whole system). */
.compare{
  grid-template-columns:minmax(0,1.34fr) minmax(0,1fr);
  gap:48px;align-items:start;
}
@media (max-width:1040px){.compare{gap:32px}}
@media (max-width:860px){.compare{grid-template-columns:minmax(0,1fr);gap:32px}}

.compare__col--good{
  background-color:var(--brand-25);
  border-color:var(--brand-100);
  padding:var(--card-pad);
  gap:24px;
}
.compare__col--good .compare__figure{
  font-size:clamp(34px,calc(11.6px + 2.84vw),56px);color:var(--brand);
}
.compare__col--base{
  background-color:transparent;border:0;border-radius:0;
  padding:var(--card-pad) 0 0;gap:24px;
}
.compare__col--base .compare__figure{
  font-size:clamp(26px,calc(18.9px + .9vw),34px);color:var(--ink-faint);
}
.compare__col--base .compare__head h2{color:var(--ink-muted)}
/* The baseline meter is a full bar and was the darkest object in the quiet
   column, which pulled the eye to the outcome nobody wants. It keeps the bar
   (a full track against a third of one is the argument at a squint) and gives
   up the contrast. */
.compare__col--base .meter span{background-color:var(--hair-strong)}
.compare__none{margin:0;max-width:38ch;font-size:15px;line-height:1.6;color:var(--ink-muted)}
/* The three rows in the optimised column are the page's whole invariant:
   solvärde + intäkt + batteri = the headline figure above. Give them the
   hairline weight of a total rather than of a list. */
.compare__col--good .kv{border-top-color:var(--hair-strong)}
.compare__col--good .kv>div:last-child{border-bottom-color:var(--hair-strong)}
.compare__col--good .kv>div:hover{background-color:rgba(2,77,104,.045)}

/* --- 3. One type ladder, and every element claims a rung ------------------
   49 distinct treatments across 6 030px, with four sizes sharing one weight
   and one colour and all doing the same job. The roles below are the whole
   set this page is allowed: display, h2, h3, lead, body, body-muted, caption,
   eyebrow, label, figure and figure-sm. Anything that did not map to one of
   them resolved to the nearest rung rather than getting a rung of its own. */

/* eyebrow — 15/500/--ink-muted. Every small label above a block. */
.savings__lbl,
.calc-controls__lbl,
.calc-col__title,
.total__label,
.ebar__title{
  font-size:15px;font-weight:500;color:var(--ink-muted);
}
.calc-controls__lbl{margin-bottom:16px}

/* caption — 14/400/--ink-faint. Notes, tags, footnotes, unit labels. */
.compare__label,
.compare__tag,
.calc-note,
.evrow__note,
.smallprint,
.assump__foot,
.meter+p{
  font-size:14px;color:var(--ink-faint);letter-spacing:0;
}
.smallprint{line-height:1.65}
.calc-note,.evrow__note{line-height:1.55}
/* The percentage under each meter was mono at 13px with +.04em tracking, which
   read as a machine caption under a bar the reader is meant to compare by eye. */
.meter+p{font-family:var(--sans);line-height:1.5}
.compare__col--good .compare__tag{color:var(--brand)}

/* body-muted — 15/400/--ink-muted. Explanatory prose inside a card. */
.assump__src,
.total__note,
.ebar__key li,
.compare__none,
.savings__qual{
  font-size:15px;line-height:1.6;color:var(--ink-muted);
}
.savings__qual{max-width:48ch;margin-top:16px}
.total__note{margin-top:0}

/* label — 16/500/--ink for a term, 16/400/--ink-muted for a row label. */
.assump__list dt,.evtoggle__name{font-size:16px;font-weight:500;color:var(--ink)}
.rrow span:first-child,.kv dt,.assump__intro{font-size:16px;font-weight:400;color:var(--ink-muted)}
.assump__intro{line-height:1.6}
.rrow span:first-child{color:var(--ink)}

/* figure-sm — 15px mono, tabular. The only place mono belongs: a column of
   values the reader compares row against row. */
.kv dd,
.rrow span:last-child,
.ebar__key b,
.evtoggle__state{
  font-family:var(--fig);font-size:15px;font-weight:400;
  font-variant-numeric:tabular-nums;color:var(--ink);letter-spacing:0;
}
.assump__val{
  font-family:var(--fig);font-size:15px;font-weight:400;
  font-variant-numeric:tabular-nums;color:var(--blue);letter-spacing:0;
}
.evtoggle__state{color:var(--ink-muted)}
.rrow small{font-size:13px;line-height:1.45;color:var(--ink-faint)}

/* --- 4. Antagandena, as a considered object -------------------------------
   The most differentiating thing on the site was rendered as the default
   appearance of a three-column table: ten identical rows, a legend, no head,
   no grouping, and the value column sharing the row's hairline with the prose
   beside it. It is the page's proof, so it gets a head that resolves in two
   columns, a rule of its own weight, and rows that breathe.
   The value column is pulled to a fixed spine and the source column to a
   readable measure, so the eye can run straight down the values (which is the
   whole reason those stay in mono) without the prose column dictating where
   they land. */
.assump{margin-top:clamp(32px,3.4vw,48px);padding-top:clamp(28px,3vw,40px);
  border-top:1px solid var(--hair-strong)}
.assump__head{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);
  gap:12px 64px;align-items:baseline;
}
.assump__title{font-size:var(--fs-h3);font-weight:600;
  letter-spacing:-.022em;line-height:1.2;text-wrap:balance}
.assump__intro{max-width:60ch;margin-top:0}
@media (max-width:820px){
  .assump__head{grid-template-columns:minmax(0,1fr);align-items:start}
  .assump__intro{margin-top:10px}
}
/* The three columns were declared in `ch`, which resolves against each element's
   OWN font: the legend is 14px Grotesk and the rows are 16px, so "Värde" sat
   42px to the left of every value it labelled. Root-relative units give the
   legend and every row one spine. */
.assump__legend,
.assump__list>div{grid-template-columns:minmax(0,15rem) minmax(0,12rem) minmax(0,1fr);
  gap:6px 40px}
.assump__legend{margin-top:clamp(24px,2.6vw,36px);padding-bottom:12px;
  border-bottom-color:var(--hair-strong)}
.assump__list>div{padding:16px 0}
.assump__src{max-width:64ch}
.assump__subtitle{margin-top:clamp(28px,3vw,40px);font-size:15px;font-weight:500;
  letter-spacing:0;color:var(--ink-muted)}
.assump__foot{margin-top:12px}
@media (max-width:1040px){
  .assump__legend,
  .assump__list>div{grid-template-columns:minmax(0,13rem) minmax(0,11rem) minmax(0,1fr)}
}
@media (max-width:820px){
  .assump__list>div{grid-template-columns:minmax(0,1fr) auto;gap:2px 16px}
  .assump__src{max-width:none}
}

/* --- 5. The drift that was left ------------------------------------------
   Four sizes at one weight and one colour, all doing one job, is the defect
   DESIGN-METRICS names. Each of these resolved to a rung that already existed
   rather than keeping a rung of its own. */
/* .ph is sized in em, so the same marker rendered at 12, 13 and 15px depending
   on what it was standing in. One size for the whole page. */
#kalkyl .ph,.assump .ph,#kostnad .ph{font-size:13px}
/* A note under a ledger row is a caption, not a fourth size. */
.rrow small{font-size:14px;color:var(--ink-faint)}
/* The profile line under the figure took a weight and a colour of its own;
   it now borrows the eyebrow's. */
.savings__qual span{font-weight:500;color:var(--ink-muted)}
/* Two greys for one job: the section meta note and every other 15px caption. */
.calc-meta .note{color:var(--ink-muted)}
.hcard__sub{color:var(--ink-faint)}
/* A row label is a row label whether it sits in a .kv or an .rrow. */
.rrow span:first-child{color:var(--ink-muted)}
.total__fig small{color:var(--ink-muted)}

/* --- 5. Spacing that is on the scale -------------------------------------- */
.savings{padding:36px 0 32px;margin-bottom:40px}
.calc-controls{margin-bottom:48px}
.evrow{margin-top:20px;gap:12px 20px}
.rrow{padding:14px 0}
.total{margin-top:28px;padding:24px}
.footnote-row{margin-top:48px;padding-top:32px}
/* ==== end: lon-design ==== */
/* ==== added: home-design ==== */
/* ==========================================================================
   index.html — the price answer, and one named type role per element
   Owner key: home-design. Everything page-specific is scoped `.home`, the
   class on <main> in index.html, because css/site.css is shared: .reason__tag
   is also on artiklar / komplettera / produkter, .pband is on komplettera, and
   a start-page decision must not travel to a page nobody asked me to change.
   ========================================================================== */

/* --- The money figure, one role for the whole start page ------------------
   Mirrors the display-figure role the lon-design block sets a few hundred
   lines above (Familjen Grotesk, 600, -.035em, tabular-nums). It is restated
   here for index's own classes on purpose: mono at display size is exactly
   what Julius called cheap on lonsamhet.html, and the start page must not be
   able to fall back to it if that block is ever re-scoped to its own page.
   If the figure role changes there, change it here in the same commit. */
.home .swing__fig,
.home .swing__sum-fig,
.home .plist__price{
  font-family:var(--sans);font-weight:600;letter-spacing:-.035em;
  font-variant-numeric:tabular-nums;
}

/* --- The price answer -----------------------------------------------------
   Was three cards whose only content was [ANGE: pris], which made the largest
   block on the start page the one with the least in it. A section may not be
   sized in proportion to a promise it cannot keep yet, so the same three bands
   are now three rows of one object. The section went from 1310px to 830px and
   is no longer the page's biggest block; the full argument about money lives
   on lonsamhet.html, which the foot links to.

   Two columns, one baseline. Left is the house and what the anläggning
   consists of; right is the price; they share a baseline in every row. The gap
   between them is the form, the same way a printed price list works, so it is
   not a void: both edges are occupied and the hairline carries the eye across.
   The three prices therefore form one right-set column, which is the thing
   three separate cards could not do — a reader compares figures by running an
   eye down them, not by looking into three boxes.

   The figure takes the display-figure role declared above, at the same size
   and colour as .swing__fig--good in the section overhead, so the two money
   objects on the page speak with one voice.

   The size sits on the slot, not on the figure, so the placeholder renders at
   the scale of the number that will replace it. Nothing about this layout
   changes on the day the three prices land: the column is already right-set,
   already tabular, already at the right size and colour. */
.home .plist{
  margin-top:40px;
  background-color:var(--surface-raised);
  border:1px solid var(--brand-100);
  border-radius:var(--radius);
}
.home .plist__row{
  display:grid;grid-template-columns:minmax(0,1fr) auto;
  gap:12px 40px;align-items:baseline;
  padding:26px clamp(20px,2.4vw,32px);
}
.home .plist__row + .plist__row{border-top:1px solid var(--hair)}
/* The 9 000 kWh row is the house .swing above is computed from. A hairline in
   the blue family, never amber: amber on this site means "click this". The
   foot says in words what the mark means, so it is never a colour the reader
   has to decode. */
.home .plist__row--ref{box-shadow:inset 2px 0 0 var(--brand)}
/* -.022em is the site's title tracking, set on .track__title and its siblings
   around line 2847; this heading takes the same role and the same value. */
.home .plist__hus h3{
  font-size:19px;font-weight:600;line-height:1.3;letter-spacing:-.022em;
  color:var(--ink);
}
.home .plist__hus p{
  margin-top:6px;font-size:15px;line-height:1.6;color:var(--ink-muted);
  text-wrap:pretty;
}
.home .plist__price{
  display:flex;flex-wrap:wrap;align-items:baseline;justify-content:flex-end;
  gap:0 10px;text-align:right;
  font-size:26px;line-height:1;color:var(--brand);
}
.home .plist__from{
  font-size:15px;font-weight:400;line-height:1.5;letter-spacing:normal;
  color:var(--ink-muted);
}
/* A marker must never break across two lines: half a placeholder on each line
   stops reading as one missing value. */
.home .plist .ph{white-space:nowrap}
/* .pbands__foot keeps its name because komplettera-med-batteri uses it too.
   The link left the paragraph: .link-arrow is an inline-flex control with a
   rule under it, so mid-sentence it inflated one line box and opened a visible
   gap in the footnote at 390px. Its own line is also a better place for an
   ask than the middle of a methodology note. */
.home .pbands__foot{margin-top:20px}
.home .plist__more{margin-top:18px}

@media (max-width:600px){
  /* Below this a 26px figure and the house name cannot share a line without
     one of them wrapping mid-word, so the price takes its own line and stays
     left-set with everything else. */
  .home .plist__row{grid-template-columns:minmax(0,1fr);gap:14px;padding:22px 20px}
  .home .plist__price{justify-content:flex-start;text-align:left}
}

/* --- One named role per text element --------------------------------------
   index.html measured 60 distinct type treatments, the worst on the site, with
   four sizes sharing weight 400 and the exact colour --ink-muted. The rules
   below do not restyle anything for its own sake; each one moves an element
   onto a role that already exists on the page.

     display   64/600   h1
     heading   48/600   h2            subheading 34/600  .h2--sm
     title     19/600   .track__title, .plist__hus h3
     lead      24/400
     body      18/400   prose
     body-sm   15/400   lists, data rows, captions inside an object
     label     15/500   .eyebrow, and any label sitting above a figure
     tag       13/500   .reason__tag, .track__tag
     footnote  14/400   methodology lines under a figure or a table
     figure    26/600 and 48/600, tabular, --brand

   What moved, and why it was drift:
     .eyebrow--faint      an eyebrow is one thing; a second, fainter eyebrow is
                          not a role, it is a leftover.
     .head-row .note      15px --ink-faint beside a 48px heading, where every
                          other supporting line on the page is --ink-muted.
     .swing__sum-lbl      the only 15/500 --ink on the page; the label beside a
                          48px figure does not need to compete with it.
     .track__foot         16px was on no scale step at all.
     .reviewbar__track    13px, unreadably small for a line carrying a figure.
     .inline-check        18px for a four-item checklist inside a panel, while
                          every other list on the page is 15px. The bold label
                          moves to --ink so the term leads and the gloss is
                          muted, which is the job the bold was already doing.
     .reason__tag         weight 400 where the identical-size .track__tag is
                          500. One tag, one weight.
     .swing__note,
     .reviewbar__note     13px --ink-faint for the same job .pbands__foot does
                          at 14px --ink-muted. Three footnotes, one treatment.
     .intent-band .note   an inline colour two per cent away from .intent__sub
                          directly beneath it. */
.home .eyebrow--faint{color:var(--ink-muted)}
.home .head-row .note{color:var(--ink-muted)}
.home .swing__sum-lbl{color:var(--ink-muted)}
.home .track__foot{font-size:15px}
.home .reviewbar__track{font-size:15px}
.home .reason__tag{font-weight:500}
.home .swing__note,.home .reviewbar__note{font-size:14px;color:var(--ink-muted)}
.home .intent-band .note{color:rgba(255,255,255,.72)}
.home .inline-check{font-size:15px}
.home .inline-check b{color:var(--ink)}
.home .inline-check svg{transform:translateY(1px)}
/* ==== end: home-design ==== */
/* ==== added: lonsamhet ==== */
/* ==========================================================================
   lonsamhet.html — structure
   The page answers the money question in the open, which is the one thing no
   competitor in the set does. Everything below either makes that answer arrive
   sooner, makes it checkable, or removes something that was in its way.

   NOTE FOR WHOEVER EDITS THIS NEXT: a second block, owner key `lon-design`,
   sits further down and owns the TYPE LADDER for this page — every font-size,
   weight, colour and caption rung. This block deliberately declares layout,
   state and geometry only, so the two do not fight whichever order they are
   emitted in. Put type there; put boxes here.
   ========================================================================== */

/* --- The section meta row -------------------------------------------------
   Replaces a whole second heading block (eyebrow + h2 + lead) that restated
   the h1 above it and pushed the headline figure 950px down the page. What is
   left is the two things that block was actually carrying: what this is, and
   which price area and period it is priced on. The band also opens tighter,
   because the h1 above it asks the question the picker answers: they are one
   thought, not two sections. */
#kalkyl{padding-top:var(--pad-md)}
.calc-meta{
  display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;
  gap:6px 24px;margin-bottom:28px;
}
.calc-meta .eyebrow{margin-bottom:0}

/* --- The answer, before the controls --------------------------------------
   The figure used to sit under the four picker cards: top edge at 877px on a
   900px screen, and 1 078px at 390. A page whose whole advantage is that it
   answers the money question before anyone asks for a name has to answer it on
   screen one. It is a block now, not a baseline row with 237px of nothing
   across the middle of it, and the qualifier sits under the number where it
   belongs instead of being marooned at the right edge. */
.savings{
  display:block;
  border-top:1px solid var(--hair);border-bottom:1px solid var(--hair);
}
.savings__lbl{margin-bottom:10px}
/* The profile line is a second line under the qualifier, not a clause inside
   it: it says which of the six combinations the figure above belongs to. */
.savings__qual span{display:block;margin-top:2px}

/* --- Two-column section heads --------------------------------------------
   A heading alone in the left half of an 1 084px row, with the paragraph that
   explains it stacked underneath and 600px of nothing beside it, is the void
   this site keeps producing. Heading left, prose right, resolved on the same
   baseline. One column below 900, where two would be 240px each. */
.splithead{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.06fr);
  gap:24px clamp(40px,4.4vw,72px);align-items:end;margin-bottom:44px;
}
@media (max-width:900px){
  .splithead{grid-template-columns:minmax(0,1fr);gap:20px;align-items:start;margin-bottom:32px}
}

/* --- The picker, and the elbil toggle that is not a fourth house ----------
   Three houses across, at any width above a phone. The fourth card in this row
   was an elbil control with no image where its neighbours had one, 60px
   narrower than them, its label 40px above theirs and 86px of empty white
   under it: a radio-shaped object that behaved differently and aligned with
   nothing. It is a switch below the row now, which is what it always was.
   The three renders were also recoloured onto one palette, one canvas and one
   ground line (scratchpad/recolour.py). Before, the unselected middle card was
   the most saturated object in the row, so the artwork fought the amber
   selection border and the row read as if two cards were chosen. */
.calc-picker{grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}
.hcard{padding-top:150px}
.hcard__img{top:14px;width:72%;height:126px}
@media (max-width:900px){.calc-picker{grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}}

.evrow{display:flex;flex-wrap:wrap;align-items:center}
.evtoggle{
  display:inline-flex;align-items:center;gap:12px;min-height:44px;
  padding:8px 18px 8px 12px;border:1px solid var(--hair);border-radius:999px;
  background:var(--surface-raised);color:var(--ink);font:inherit;
  cursor:pointer;transition:border-color .2s ease,box-shadow .2s ease;
}
.evtoggle:hover{border-color:var(--ink-faint)}
.evtoggle:focus-visible{outline:2px solid var(--blue);outline-offset:3px}
.evtoggle__track{
  position:relative;flex:none;width:38px;height:22px;border-radius:999px;
  background-color:var(--surface-sunken);border:1px solid var(--hair-strong);
  transition:background-color .2s ease,border-color .2s ease;
}
.evtoggle__knob{
  position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;
  background-color:var(--surface-raised);box-shadow:0 1px 2px rgba(14,26,32,.24);
  transition:transform .2s var(--ease-out);
}
.evtoggle[aria-pressed="true"]{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}
.evtoggle[aria-pressed="true"] .evtoggle__track{background-color:var(--accent);border-color:var(--accent)}
.evtoggle[aria-pressed="true"] .evtoggle__knob{transform:translateX(16px)}
.evtoggle[aria-pressed="true"] .evtoggle__state{color:var(--ink)}
.evrow__note{max-width:46ch}

/* --- The optimised column's meter has a fill ------------------------------
   Two other blocks now style .compare, and both set the baseline meter and
   neither sets this one, so the bar the whole "34 % of the cost" argument
   rests on rendered as an empty track. */
.compare__col--good .meter span{background-color:var(--brand)}

/* --- .kv stays two columns here on a phone --------------------------------
   The sitewide rule stacks label over value below 640, which is right for the
   policy pages it was written for, where the value is a sentence. Here every
   value is "5 600 kr" and stacking turned three tidy rows into six, and broke
   the visual sum against the headline. The longest label plus the widest value
   measure 269px inside a 310px card at 390. */
@media (max-width:640px){
  #kalkyl .kv>div{flex-wrap:nowrap;gap:12px 16px}
  #kalkyl .kv dt{flex:0 1 auto}
  #kalkyl .kv dd{flex:0 0 auto;text-align:right}
}

/* --- The footnote pair reads from the top, not the bottom ---------------- */
#kalkyl .footnote-row{align-items:start}

/* --- The picker on a phone ------------------------------------------------
   Three cards side by side at 390 leaves ~104px each, which is too narrow for
   "Mellanstort hus" plus its description. Each card becomes a row instead:
   render, name, description, state. Full width, one decision per line, and the
   whole picker costs about 210px rather than 800. */
@media (max-width:560px){
  .calc-picker{grid-template-columns:minmax(0,1fr);gap:10px}
  .hcard{
    display:grid;grid-template-columns:78px minmax(0,1fr) 20px;
    grid-template-rows:auto auto;align-items:center;gap:0 14px;padding:10px 14px;
  }
  .hcard__img{position:static;transform:none;grid-column:1;grid-row:1 / span 2;
    width:78px;height:56px;align-self:center}
  .hcard__check{grid-column:3;grid-row:1 / span 2;margin-bottom:0;align-self:center}
  .hcard__name{grid-column:2;grid-row:1}
  .hcard__sub{grid-column:2;grid-row:2}
  .hcard:hover{transform:none}
}

/* --- The left column's hole ----------------------------------------------
   Both columns of .calc-grid are stretched to the same height by the grid, but
   the left held four rows against the right's five plus a total box. Pinning
   the note to the floor with margin-top:auto did not remove the 193px hole, it
   moved it to the middle of the column, inside the most important card on the
   page. The column now carries the same year of solel split three ways, which
   is a figure the reader wants and which the model already computes:
   egenIdag + fangat + expMed, summing to the annual production. */
.calc-col{display:flex;flex-direction:column}
.calc-note{margin-top:auto;padding-top:24px}
@media (max-width:860px){.calc-note{margin-top:16px;padding-top:0}}

.ebar{margin:28px 0 0}
.ebar__title{margin-bottom:12px}
.ebar__track{display:flex;height:12px;border-radius:999px;overflow:hidden;
  background-color:var(--surface-sunken)}
.ebar__seg{display:block;height:100%;transition:width .35s var(--ease-out)}
.ebar__seg--direkt{background-color:var(--brand)}
.ebar__seg--batt{background-color:var(--brand-300)}
.ebar__seg--sald{background-color:var(--brand-100)}
.ebar__key{display:grid;gap:9px;margin:15px 0 0;padding:0;list-style:none}
.ebar__key li{display:flex;align-items:baseline;gap:10px}
.ebar__key b{margin-left:auto;white-space:nowrap}
.ebar__dot{flex:none;width:9px;height:9px;border-radius:2px;transform:translateY(1px)}
.ebar__dot--direkt{background-color:var(--brand)}
.ebar__dot--batt{background-color:var(--brand-300)}
.ebar__dot--sald{background-color:var(--brand-100);box-shadow:inset 0 0 0 1px var(--hair-strong)}

/* --- The post that is always zero ----------------------------------------
   K.avgift is 0, so "Sänkta effektavgifter" reads 0 kr in all six house and
   elbil combinations. The row stays, because it is a real term in the model
   and a reader whose nätägare does charge effekt should see where the upside
   would land, but it says so itself instead of printing a bare 0 under a
   heading that promised the post was counted. */
.rrow small{display:block;margin-top:4px}
.rrow--nil span:last-child{color:var(--ink-faint)}

/* --- Antagandena ----------------------------------------------------------
   One row per constant in K in js/kalkyl.js, in K's own order: what it is,
   what it stands on, where it comes from. Two constants (manader, ctimme) had
   no row at all and fangstTak was printed sixth while it is last in the file,
   under a sentence promising this was the whole list. On the one page whose
   pitch is "check our arithmetic" that was the most damaging line on the site.
   The two rows that are not constants now sit under their own subheading. */
.assump{border-top:1px solid var(--hair-strong)}
.assump__legend,
.assump__list>div{display:grid;align-items:baseline}
.assump__legend{
  border-bottom:1px solid var(--hair-strong);
  font-family:var(--sans);font-size:var(--fs-mono);
}
.assump__list>div{border-bottom:1px solid var(--hair)}
.assump__src{text-wrap:pretty}
.assump__list--extra{margin-top:10px}
@media (max-width:820px){
  .assump__legend{display:none}
  .assump__val{text-align:right}
  .assump__src{grid-column:1 / -1;margin-top:6px}
}

/* --- The band behind the calculation --------------------------------------
   This slot used to be "Fyra intäktsposter, inte en": 563px restating in prose
   the breakdown table two screens above it, and contradicting it on the
   effektavgift post. Nothing on 6 030px of this page was a photograph and no
   trust element existed anywhere in the body, while all four benchmark sites
   put a photo and a review count above their fold. The band carries both now,
   and the four revenue posts are one paragraph beside the table they describe.
   The rail is Otovo's construction: two-line stacks, no icons, no badges.
   Named .bakom, not .proof: om-oss.html already owns a .proof, and inheriting
   its margin, padding and border-top drew a stray hairline across this band. */
.bakom{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(32px,4.4vw,64px);align-items:stretch;
}
/* The photograph fills its column rather than sitting centred in it: at 5:4 it
   was 141px shorter than the text beside it and the band read as two objects
   of different weight. */
.bakom__media{display:flex;margin:0}
.bakom__media img{
  display:block;width:100%;height:100%;aspect-ratio:5/4;object-fit:cover;
  border-radius:var(--radius);transform:translateZ(0);
}
.bakom__rail{margin:28px 0 0;padding:0;list-style:none;border-top:1px solid rgba(255,255,255,.24)}
.bakom__rail li{border-bottom:1px solid rgba(255,255,255,.24)}
.bakom__rail a{display:flex;flex-direction:column;gap:3px;padding:13px 0;text-decoration:none}
.bakom__rail b{font-size:16px;font-weight:600;letter-spacing:-.012em;color:var(--dark-fg)}
.bakom__rail span{font-size:14px;color:rgba(255,255,255,.78)}
.bakom__rail a:hover b{text-decoration:underline;text-underline-offset:3px}
.bakom__body .h2{color:var(--dark-fg)}
@media (max-width:900px){
  .bakom{grid-template-columns:minmax(0,1fr);gap:28px}
  .bakom__media img{height:auto;aspect-ratio:16/10}
}


/* --- Small print ----------------------------------------------------------
   Replaces three style="font-size:14px;line-height:1.6;color:…" attributes
   that were the same declaration written out three times. */
.smallprint{text-wrap:pretty}
.smallprint a{color:var(--brand);text-decoration:underline;text-underline-offset:2px;text-decoration-thickness:1px}
.smallprint a:hover{color:var(--brand-600)}

/* --- Mid-page links are tap targets on a phone ---------------------------
   The two .link-arrow asks measured 24px tall and sat 12px apart at 390: both
   under the 44px target and close enough to mis-tap. The rule stays tight
   under the text (flex-end) so the link still reads as a link rather than as a
   block with a stray line under it.
   Scoped to this band rather than to #main: .link-arrow is used on every page
   and the same fix belongs sitewide, but those pages have their own owners. */
@media (max-width:640px){
  #bakom .link-arrow{min-height:44px;align-items:flex-end;padding-bottom:6px}
  #bakom .btn-row--tight{gap:20px}
}
/* ==== end: lonsamhet ==== */
/* ==== added: convert ==== */
/* ==========================================================================
   Konverteringsytorna — boka.html, kontakt.html, tack.html
   Owner key: convert. Extends the chrome and system blocks above; nothing here
   restates a rule they already set.

   The one idea: the two pages a buyer converts on now open on a photograph of
   one of our own installations, full-bleed under the same two-layer scrim
   index.html uses, with the white panel (the form, the contact rail) floating
   on it. Before this they were the only surfaces in the benchmark set with no
   image above the fold at all — flat #F1F5F7, black type, a white box — and
   side by side with otovo.se that is the whole difference between "competent"
   and "expensive".

   Everything else follows from it: white type on the left column, amber freed
   up for the submit button, and the four grey stripes under the lead given
   different weights so the column has a shape at a squint.
   ========================================================================== */

/* --- Bare <h3> in a .tile inherits the UA's 700 --------------------------
   The system declares three weights and never 700, and .tile h3 is the one
   heading on the site that is authored as a bare tag with no .h3 class, so it
   was rendering bolder than every real heading around it. Weight and tracking
   only: the size is left alone so .tile--sm's 19px on steg-for-steg still
   wins on its own terms. */
.tile h3{font-weight:600;letter-spacing:-.022em;line-height:1.3}

/* En dash in a range must not be broken through. "vardagar 9–18" was landing
   as "8–" / "18." at 390. */
.nowrap{white-space:nowrap}

/* ==========================================================================
   The photographic band, shared by boka.html and kontakt.html
   ========================================================================== */
.quote--media,.reach--media{
  position:relative;overflow:hidden;
  background-color:var(--brand-900);
  color:var(--dark-fg);
}
.quote--media > .container,.reach--media > .container{position:relative;z-index:1}
.band-media{position:absolute;inset:0;z-index:0;margin:0}
.band-media img{width:100%;height:100%;object-fit:cover;object-position:center 54%}
/* Two layers, the recipe index.html's hero already uses and Otovo's scrim
   describes: a horizontal fade that is near-solid brand blue where the type
   sits and releases toward the form side so the roof stays a photograph, plus
   a gentle vertical darkening that seats the white header above and hands off
   to the band below. Painted as background-image, never the shorthand. */
.band-media__scrim{
  position:absolute;inset:0;
  background-image:
    linear-gradient(90deg,
      rgba(1,44,60,.88) 0%,
      rgba(1,44,60,.82) 30%,
      rgba(1,44,60,.62) 52%,
      rgba(1,44,60,.46) 74%,
      rgba(1,44,60,.42) 100%),
    linear-gradient(180deg,
      rgba(1,44,60,.32) 0%,
      rgba(1,44,60,.06) 32%,
      rgba(1,44,60,.20) 66%,
      rgba(1,44,60,.62) 100%);
}
/* kontakt's frame puts white window frames exactly where the lede sits, and
   its panel is narrower than boka's, so the type side can take a heavier fade
   without losing the picture. Measured before this: the lede sat at 4.38:1,
   which only clears AA as large text and stops clearing it below 1200px where
   --fs-lead falls under 24px. */
.reach--media .band-media__scrim{
  background-image:
    linear-gradient(90deg,
      rgba(1,44,60,.93) 0%,
      rgba(1,44,60,.89) 30%,
      rgba(1,44,60,.70) 52%,
      rgba(1,44,60,.52) 74%,
      rgba(1,44,60,.48) 100%),
    linear-gradient(180deg,
      rgba(1,44,60,.34) 0%,
      rgba(1,44,60,.12) 32%,
      rgba(1,44,60,.24) 66%,
      rgba(1,44,60,.62) 100%);
}
@media (max-width:980px){
  /* One column, so the photograph stops being a background and becomes the
     first thing on the page — full-bleed, 16:10, exactly how all four
     benchmark sites open on a phone. The type then sits on the flat deep
     ground under it, where it is unambiguously legible. */
  .quote.quote--media,.reach.reach--media{padding-top:0}
  .band-media{position:relative;inset:auto;aspect-ratio:16/10}
  .band-media img{position:static}
  .band-media__scrim{
    background-image:linear-gradient(180deg,
      rgba(1,44,60,.42) 0%,
      rgba(1,44,60,.10) 34%,
      rgba(1,44,60,.22) 66%,
      rgba(1,44,60,.86) 100%);
  }
  .quote--media > .container,.reach--media > .container{padding-top:clamp(30px,7vw,44px)}
}

/* ==========================================================================
   boka.html — the quote surface
   ========================================================================== */
.quote{padding:var(--pad-md) 0 var(--pad-lg)}
.quote__grid{display:grid;gap:36px;align-items:start}
.quote__intro .eyebrow{margin-bottom:16px;color:var(--dark-fg)}
.quote__intro h1{margin-bottom:24px}
.quote__intro .lead{color:var(--dark-body)}
/* The default [data-enter] ramp is 180ms a step, which puts the fifth item
   almost 1.1s out. Same tightening .page-head already applies. */
.quote [data-enter],.reach [data-enter]{animation-delay:calc(var(--i,0) * 90ms + 60ms)}

@media (min-width:981px){
  /* Slightly under half to the argument, slightly over half to the form: the
     form is the page. The left column is three stacked blocks and the form
     spans all three, so the third row takes 1fr and the review plus the phone
     line are pinned to its end. The slack then falls between two finished
     blocks, over the photograph, instead of opening a hole under the lead.
     Source order is intro → promises → form → proof, which is what a phone
     and the keyboard get, and it puts the three reasons to start typing above
     the first field at 390. */
  .quote__grid{
    grid-template-columns:minmax(0,.86fr) minmax(0,1fr);
    grid-template-rows:auto auto 1fr;
    column-gap:clamp(40px,4.6vw,72px);
    row-gap:32px;
  }
  .quote__intro{grid-column:1;grid-row:1}
  .quote__support{grid-column:1;grid-row:2;align-self:start}
  .quote__proof{grid-column:1;grid-row:3;align-self:end}
  .quote__panel{grid-column:2;grid-row:1 / -1}
}

/* The one raised object on the page, and now the only bright one: white on a
   scrimmed photograph, no border needed and no shadow allowed. */
.quote__panel{
  background-color:var(--surface-raised);
  border:1px solid var(--hair);
  border-radius:var(--radius);
  padding:clamp(22px,2.4vw,36px);
  color:var(--ink);
}
.quote--media .quote__panel{border-color:transparent}
.quoteform__head h2{margin-bottom:10px}
.quoteform__intro{
  max-width:44ch;
  font-size:16px;line-height:1.55;color:var(--ink-muted);text-wrap:pretty;
}
/* .form's own 32px gap is right between the two field groups and too much
   between a legend and its fields. */
.quoteform .fieldset__legend{
  padding-bottom:10px;margin-bottom:18px;
  border-bottom:1px solid var(--hair);
  width:100%;
}
.quoteform .form__hint{margin-top:2px}
.quoteform .form__actions{padding-top:4px}

/* --- The two selects were the only unstyled controls in the form ----------
   Default Chrome chevron and default metrics, sitting directly under six
   custom-bordered inputs: they read as a different decade. Same border,
   radius, padding and height as .input, with the site's own caret glyph (the
   one in .nav-more__caret) painted in --ink-faint. */
.select{
  appearance:none;-webkit-appearance:none;
  padding-right:42px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%237C8E96' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 15px center;
  background-size:10px 6px;
}
.select:hover{border-color:var(--hair-strong)}

/* --- The left column, given a shape ---------------------------------------
   It used to be five visually identical stripes: hairline, promise, hairline,
   promise, hairline, promise, hairline, review, photo, hairline, phone. Same
   weight, same grey, same rhythm, no hierarchy at a squint. The rules between
   the promises are gone, the review carries a rule of its own and a larger
   setting because it is the only third-party voice here, and every remaining
   gap is 20, 24 or 32. */
.promise{display:flex;flex-direction:column;gap:20px}
.promise li{display:flex;flex-direction:column;gap:3px}
.promise b{font-size:16px;font-weight:600;letter-spacing:-.012em;color:var(--ink)}
.promise span{font-size:15px;line-height:1.5;color:var(--ink-muted);text-wrap:pretty}
.quote--media .promise b{color:var(--dark-fg)}
.quote--media .promise span{color:var(--dark-body)}

/* Rating + one verbatim review, at the point of the click. */
.proofquote{border-left:2px solid var(--brand-300);padding-left:22px}
.proofquote__rating{
  display:inline-flex;align-items:center;gap:10px;
  font-size:14px;color:var(--ink-muted);text-decoration:none;
}
.proofquote__rating b{color:var(--ink)}
.proofquote__rating:hover b{text-decoration:underline;text-underline-offset:3px}
.proofquote blockquote{
  margin:14px 0 0;max-width:44ch;
  font-size:19px;line-height:1.45;letter-spacing:-.012em;
  color:var(--ink);text-wrap:pretty;
}
.proofquote figcaption{
  margin-top:10px;
  font-size:14px;font-weight:500;color:var(--ink-faint);
}
.quote--media .proofquote__rating{color:var(--dark-body)}
.quote--media .proofquote__rating:hover{color:#fff}
.quote--media .proofquote__rating b{color:var(--dark-fg)}
.quote--media .proofquote blockquote{color:var(--dark-fg)}
.quote--media .proofquote figcaption{color:var(--dark-body)}

/* The route for the share of buyers who will not fill in a form at all, and
   the page's one home for the phone number outside the chrome. */
.quote__alt{
  margin-top:24px;padding-top:20px;border-top:1px solid var(--hair);
  font-size:15px;color:var(--ink-muted);
}
.quote__alt a{
  font-weight:600;color:var(--ink);
  text-decoration:underline;text-underline-offset:3px;
  text-decoration-color:var(--hair-strong);
}
.quote__alt a:hover{color:var(--brand);text-decoration-color:currentColor}
.quote--media .quote__alt{border-top-color:var(--dark-hair);color:var(--dark-body)}
.quote--media .quote__alt a{color:var(--dark-fg);text-decoration-color:var(--dark-border)}
.quote--media .quote__alt a:hover{color:#fff;text-decoration-color:currentColor}

@media (max-width:980px){
  /* Stacked, the proof follows the submit button, so it needs its own air. */
  .quote__proof{margin-top:8px}
}
@media (max-width:520px){
  /* Two selects side by side stop being readable before .form__grid's own
     700px breakpoint does, because the panel is narrower than the viewport. */
  .quoteform .form__grid{grid-template-columns:minmax(0,1fr)}
}

/* ==========================================================================
   The section head, on all three pages
   Two 13px links used to sit level with the second line of the H2, 670px away
   across an empty middle, reading like breadcrumbs somebody forgot to delete.
   Under the heading is where the reader who just finished the sentence is
   looking, and it costs the band nothing.
   ========================================================================== */
.deliver__head{margin-bottom:clamp(34px,3.6vw,48px)}
/* .btn-row goes to a stretched column below 640, which is right for buttons
   and wrong for two 13px links: they ended up 64px apart down the left edge.
   These stay a wrapping row. */
.deliver__head .btn-row{flex-direction:row;flex-wrap:wrap;align-items:center;gap:14px 24px}
/* "Fyra saker, innan en enda panel foreslas." left "foreslas." alone on line
   two at 1440: a one-word widow at display size. The measure is capped and the
   two lines are balanced, so the fix is in the layout and not in the words. */
.deliver__head h2{margin-bottom:0;text-wrap:balance}
/* Four columns at 247px set every sentence at 27ch when nothing else on the
   page is under 38ch, and all four ragged. Two by two gives each deliverable a
   real measure. */
@media (min-width:641px){
  .deliver{grid-template-columns:repeat(2,minmax(0,1fr));column-gap:clamp(40px,5vw,80px);row-gap:34px}
}

/* ==========================================================================
   kontakt.html — the channels sit in the panel, on the photograph
   ========================================================================== */
.reach{padding:var(--pad-md) 0 var(--pad-lg)}
.reach__grid{display:grid;gap:36px;align-items:start}
.reach__intro .eyebrow{margin-bottom:16px;color:var(--dark-fg)}
.reach__intro h1{margin-bottom:24px}
.reach__intro .lead{color:var(--dark-body)}
.reach__aside{
  margin-top:26px;padding-top:20px;border-top:1px solid var(--dark-hair);
  max-width:46ch;font-size:15px;color:var(--dark-body);
}
.reach__aside a{
  font-weight:600;color:var(--dark-fg);
  text-decoration:underline;text-underline-offset:3px;
  text-decoration-color:var(--dark-border);
}
.reach__aside a:hover{color:#fff;text-decoration-color:currentColor}

@media (min-width:981px){
  .reach__grid{
    grid-template-columns:minmax(0,1fr) minmax(0,.86fr);
    column-gap:clamp(40px,4.6vw,72px);
  }
  .reach__panel{margin-top:6px}
}

.reach__panel{
  background-color:var(--surface-raised);
  border:1px solid transparent;
  border-radius:var(--radius);
  padding:clamp(20px,2.2vw,32px);
  color:var(--ink);
}
.reach__list li{
  display:grid;grid-template-columns:auto minmax(0,1fr);gap:16px;
  padding:20px 0;border-top:1px solid var(--hair);
}
.reach__list li:first-child{padding-top:0;border-top:0}
.reach__list li:last-child{padding-bottom:0}
.reach__ico{width:20px;height:20px;color:var(--brand);margin-top:6px}
.reach__label{
  display:block;margin-bottom:2px;
  font-size:var(--fs-mono);color:var(--ink-faint);
}
.reach__value{
  display:block;
  font-size:var(--fs-h3);font-weight:600;letter-spacing:-.022em;line-height:1.25;
  text-decoration:none;overflow-wrap:anywhere;
}
.reach__value:hover{color:var(--brand-600)}
.reach__note{
  display:block;margin-top:6px;
  font-size:15px;line-height:1.5;color:var(--ink-muted);text-wrap:pretty;
}

/* ==========================================================================
   tack.html — what happens, and when
   ========================================================================== */
.next{border-top:1px solid var(--hair)}
.next li{
  display:grid;grid-template-columns:minmax(0,15rem) minmax(0,1fr);
  gap:16px 36px;
  padding:15px 0;border-bottom:1px solid var(--hair);
}
/* The timing column, not a step number: the README rules out 01/02/03 in any
   form, and "when" is the only thing this page is actually being asked. */
.next__when{
  font-size:15px;font-weight:500;color:var(--brand);
  text-wrap:pretty;
}
/* Samma nedskalning som .specs och .skal fick 1 augusti. */
.next__body h3{
  margin-bottom:5px;
  font-size:18px;font-weight:600;letter-spacing:-.018em;line-height:1.3;
}
.next__body p{max-width:62ch;font-size:15.5px;line-height:1.55;color:var(--ink-muted);text-wrap:pretty}
.next__foot{
  margin-top:28px;max-width:66ch;
  font-size:15px;line-height:1.6;color:var(--ink-muted);text-wrap:pretty;
}
.next__foot a{
  font-weight:600;color:var(--ink);
  text-decoration:underline;text-underline-offset:3px;
  text-decoration-color:var(--hair-strong);
}
.next__foot a:hover{color:var(--brand);text-decoration-color:currentColor}
@media (max-width:760px){
  .next li{grid-template-columns:minmax(0,1fr);gap:10px;padding:22px 0}
}

/* ==========================================================================
   The closing band, on these three pages only
   The band is 1084px wide and holds 560px of copy and a 213px button stack,
   so at 1440 there was a 330px hole down its middle: a wide shallow strip with
   text at one end, two small buttons at the other and nothing between. The
   inner grid is capped and centred so the two halves meet, and the actions
   column shrinks to its buttons instead of being a 471px box with its content
   pushed to the far edge.
   Scoped to .page-convert because .closer is stamped from partials/ onto
   thirteen pages and the same fix belongs there, not here. See handoffs.
   ========================================================================== */
.page-convert .closer__inner{
  max-width:940px;margin-inline:auto;
  grid-template-columns:minmax(0,1fr) max-content;
  column-gap:clamp(36px,4.4vw,64px);
}
.page-convert .closer__body p{max-width:56ch}
@media (max-width:900px){
  .page-convert .closer__inner{grid-template-columns:minmax(0,1fr);max-width:none}
}

/* Amber means "click this", and on boka.html the thing to click is the submit
   button inside the form. The closer's phone link is the fallback route, so it
   takes the same white-on-deep treatment .btn--primary already has on a dark
   ground. Every other page keeps its amber closer. */
.page-boka .closer .btn--accent{
  background:var(--surface-raised);color:var(--ink);box-shadow:none;
}
.page-boka .closer .btn--accent:hover{
  background:#e7eae9;color:var(--ink);box-shadow:none;
}
/* ==== end: convert ==== */

/* ==== added: legal ==== */
/* ==========================================================================
   Document pages — villkor.html and integritetspolicy.html (body.page-doc),
   plus one @media print block that is deliberately sitewide.

   Selectors here carry one extra step of specificity wherever they restate a
   rule from an earlier owner block, so the fix survives whichever agent
   appends last. Everything else is a name that exists nowhere else.
   ========================================================================== */

/* --- Print: the site must not print blank ---------------------------------
   The single action a cautious buyer takes on a terms page or a privacy policy
   is Cmd+P before signing. Every [data-reveal] is authored at opacity:0 and is
   only cleared by the IntersectionObserver in site.js, which never fires for a
   section that was never scrolled into a viewport. Printing therefore produced
   the hero, one section, roughly six blank A4 pages, and the footer: on
   villkor.html the closing CTA with the phone number, "Bokning och avtal" and
   the whole consumer-rights block were simply not on the paper. Same failure
   on integritetspolicy.html (3 of 4 sections), lonsamhet.html and index.html.

   The prefers-reduced-motion block already carries exactly the right rule;
   print needs the same one. [data-enter] is added because its keyframe may not
   have run either when the print dialog opens on a fresh load.

   The dark grounds need the second half. Chrome does not print background
   colours by default, so .section--blue and .closer would print white type on
   white paper — worse than blank, because it still looks right on screen. They
   are forced back to ink on white for print only.
   ------------------------------------------------------------------------- */
@media print{
  .js [data-reveal]{opacity:1!important;transform:none!important;transition:none!important}
  [data-enter],.hero{animation:none!important;opacity:1!important;transform:none!important}

  .utility,.hero,.section--blue,.section--dark,.section--deep,.closer,.router{
    background-color:#fff!important;color:var(--ink)!important;
  }
  .utility *,.hero *,.section--blue *,.section--dark *,.section--deep *,
  .closer *,.router *{color:var(--ink)!important}
  .utility a,.section--blue a,.section--dark a,.section--deep a,.closer a{
    color:var(--brand)!important;
  }
  .btn{border:1px solid var(--ink-faint)!important;background:transparent!important}

  /* Paper is expensive. The screen ladder of 72–112px bands would put roughly
     one section on each sheet. */
  .section{padding:26px 0!important}
  .page-head{padding-top:16px;padding-bottom:20px}
  .skip-link,.site-nav,.nav-toggle,.site-header__panel,.site-header__cta,
  .socials,.deco,.tilebg{display:none!important}
  .site-header,.site-footer{border-color:var(--hair)!important}

  h1,h2,h3,h4{break-after:avoid}
  .kv>div,.tile,.rule-list li,.docnav__list li,.idcard{break-inside:avoid}
  .section,.closer{break-inside:auto}
}

/* --- The document head ----------------------------------------------------
   Both pages ran the page head as a single left column: the h1 stopped around
   x=780 at 1440 and the right 660px stood empty, while every band below was
   empty on the left. The eye tracked a diagonal of nothing down the page.
   The right half now carries what a careful reader of a legal document looks
   for before reading it: which questions the page answers, and how old it is.
   Nothing in it is a new claim — the section names, and the date line that was
   already on the page. The two columns bottom-align, so the index reads as the
   document's spine rather than as a floating box. */
.page-head--doc{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,340px);
  gap:clamp(40px,4.4vw,72px);
  align-items:end;
}
.page-head--doc .page-head__text{min-width:0}
.page-head--doc h1{margin-bottom:20px}
.page-head--doc .lead{margin-bottom:0}
@media (max-width:900px){
  .page-head--doc{grid-template-columns:minmax(0,1fr);gap:32px;align-items:start}
}

.docnav{min-width:0}
/* --ink-muted, not --ink-faint: #7C8E96 on the --surface ground is 3.1:1,
   which fails AA for text at any size. The label and the descriptions are new
   here, so they are not inheriting that; the one thing left faint is the scope
   line in .docmeta, where the site's existing token usage stands. */
.docnav__label{
  margin-bottom:12px;
  font-family:var(--fig);font-size:var(--fs-mono-sm);
  letter-spacing:.06em;text-transform:uppercase;color:var(--ink-muted);
}
.docnav__list{border-top:1px solid var(--hair)}
.docnav__list li{border-bottom:1px solid var(--hair)}
.docnav__list a{
  display:flex;flex-direction:column;gap:2px;
  padding:11px 0;text-decoration:none;
}
.docnav__list b{font-size:16px;font-weight:500;color:var(--ink)}
.docnav__list span{font-size:14px;line-height:1.4;color:var(--ink-muted);text-wrap:pretty}
.docnav__list a:hover b{color:var(--blue)}
.docnav__list a:focus-visible{outline-offset:2px;border-radius:var(--radius-sm)}
.docnav .docmeta{max-width:none;margin-top:22px;padding-top:14px}
/* 11px of padding either side of a 22px line is a 44px box on a phone, which
   is the minimum tap target; on desktop the rows are pointer-sized already. */
@media (max-width:900px){
  .docnav__list a{padding:13px 0}
}

/* --- The document line ----------------------------------------------------
   A policy with no date cannot be trusted: the reader has no way to know
   whether it describes today's site. It sits in the mono face because the date
   is a figure, and behind a hairline so it reads as document metadata.
   The whole line used to be set at the faintest ink on the page — in the
   print-media render it was the least legible element in the document, fainter
   than the body it sat above. That is backwards: the date is content, the
   scope line is metadata, so the two halves are now a step apart. */
.docmeta{
  display:flex;flex-wrap:wrap;align-items:baseline;
  gap:6px 28px;
  max-width:60ch;
  margin-top:30px;padding-top:14px;
  border-top:1px solid var(--hair);
  font-family:var(--fig);
  font-size:var(--fs-mono-sm);
  line-height:1.5;
  color:var(--ink-faint);
}
.docmeta__date{color:var(--ink-muted)}
.docmeta__date b{font-weight:500;color:var(--ink)}
.docmeta time{font-variant-numeric:tabular-nums}
@media (max-width:640px){
  .docmeta{gap:4px;margin-top:24px}
  .docmeta span{flex:1 0 100%}
}

/* --- Heading row over a wide grid -----------------------------------------
   The shape that replaces two of the four label/content splits. A .split whose
   left column is 144px of heading against a 647px right column is not two
   columns, it is one column and a hole; four of them in sequence is a
   checkerboard with no shape at all. Where the content is a set of peer items
   (the four questions the offer answers; the three third-party hosts), the
   band is a heading row over a full-width grid instead: the width gets used,
   and the items read as peers rather than as a bullet list stuffed into half
   the page. */
.doc-head{align-items:end;gap:28px 64px;margin-bottom:clamp(40px,4vw,54px)}
.doc-head__title{flex:0 1 42%;min-width:0}
.doc-head__lede{flex:0 1 50%;max-width:52ch;min-width:0}
@media (max-width:900px){
  .doc-head{display:block}
  .doc-head__lede{max-width:60ch;margin-top:22px}
}

.doc-tiles h4{
  margin-bottom:8px;
  font-size:19px;font-weight:600;letter-spacing:-.02em;line-height:1.25;
}
.doc-tiles .key-mono{
  font-family:var(--fig);font-size:17px;font-weight:500;letter-spacing:-.005em;
}
.doc-tiles p{text-wrap:pretty}

/* On the saturated ground the tile hairline is --hair (a light neutral) and
   the hover state is --brand, which is the ground itself. Both restated. */
.page-doc .section--blue .tile{border-top-color:rgba(255,255,255,.26)}
.page-doc .section--blue .tile:hover{border-top-color:var(--accent)}
.page-doc .section--blue .note{color:rgba(234,243,247,.72)}
.page-doc .section--blue .note a{color:#fff;text-decoration:underline;text-underline-offset:3px}

/* --- The company card -----------------------------------------------------
   "Dina rättigheter" was the worst band on villkor.html: 144px of label
   against 639px of content. Both columns now carry real matter, the law on the
   left and who you are dealing with on the right, so the row resolves on its
   own height (418 against 412) instead of needing a sticky label to disguise
   the gap. */
.idcard{
  background-color:var(--brand-25);
  border-radius:var(--radius);
  padding:clamp(24px,2.4vw,34px);
}
.idcard .kv{border-top-color:var(--brand-100)}
.idcard .kv>div{border-bottom-color:var(--brand-100)}
.idcard .kv>div:hover{background:rgba(2,77,104,.045)}
.idcard .note{color:var(--ink-muted)}
.section--tint .idcard{background-color:var(--surface-raised)}

/* --- .kv--prose lost its face --------------------------------------------
   .kv--prose exists so a value that is a sentence is not set in the numeric
   face. A later owner block restates `.kv dd{font-family:var(--fig)}` at the
   same specificity, so on both policy pages every prose value was rendering as
   a monospaced column again — "Typvärden per storleksklass, elprisområde SE3"
   in IBM Plex Mono, right-set. Restated one step higher so cascade order stops
   mattering. HANDOFF: the cleaner fix is for that block to scope its rule to
   the pages it means. */
.page-doc .kv--prose dd{
  font-family:var(--sans);font-size:16px;font-weight:400;
  font-variant-numeric:normal;letter-spacing:normal;
  color:var(--ink);overflow-wrap:normal;white-space:normal;
}

/* Jumping from the index should not land a section flush against the top edge
   of the viewport. The header is position:relative, so this is air, not
   clearance. */
.page-doc .section[id]{scroll-margin-top:var(--pad-sm)}

/* --- The sticky label's offset --------------------------------------------
   `.split > .self-start{top:104px}` was clearing nothing: the site header is
   115px tall and computes position:relative, so there is no fixed chrome above
   the scroll and 104 was an arbitrary number off the spacing scale. Neither
   document page uses .self-start any more — the columns resolve on their own
   height instead — but varfor-oss.html does, so the offset is put back on the
   scale rather than deleted. */
@media (min-width:981px){
  body .split > .self-start{top:var(--pad-lg)}
}

/* --- A sentence in a key/value row reads from a left edge ------------------
   .kv is right-set because it was built for figures, and a right edge is what
   lets an eye run down a column of numbers. A sentence has no such column: on
   integritetspolicy.html "Underlaget sparas så länge bokförings- och
   garantiskäl kräver" broke with the single word "kräver" alone on a right-set
   second line, twice on one page. On the document pages the prose variant
   becomes a real two-column definition grid instead — labels aligned in their
   own column, values reading from one left edge. Figures keep the right edge:
   the company details on villkor.html are a plain .kv and are untouched.
   Scoped to .page-doc because konkurs.html uses .kv--prose too. */
@media (min-width:641px){
  .page-doc .kv--prose{
    display:grid;grid-template-columns:minmax(12ch,auto) minmax(0,1fr);
    border-top:1px solid var(--hair);
  }
  .page-doc .kv--prose > div{display:contents}
  .page-doc .kv--prose dt,
  .page-doc .kv--prose dd{padding:12px 0;border-bottom:1px solid var(--hair)}
  .page-doc .kv--prose dt{padding-right:32px}
  .page-doc .kv--prose dd{text-align:left}
}

/* --- A .link-arrow is a 24px tap target on a phone ------------------------
   The underline is the affordance, so padding cannot be added without moving
   the rule away from the text. The hit box is expanded instead, which leaves
   the drawing untouched. Both places it appears on these pages sit alone in
   their own block, so nothing is overlapped. */
@media (max-width:900px){
  .page-doc .link-arrow{position:relative}
  .page-doc .link-arrow::after{
    content:"";position:absolute;left:0;right:0;top:50%;
    height:44px;transform:translateY(-50%);
  }
}
/* ==== end: legal ==== */
/* ==== added: home ==== */
/* ==========================================================================
   index.html — start page
   Owner key: home. Everything here is scoped to a component the start page
   owns, or to `.home` where the component is shared. Nothing restates a rule
   the base sheet, the system pass or the home-design pass already has.
   ========================================================================== */

/* --- NOT MINE, and it must not ship: the overlay header goes white ---------
   `.site-header--overlay{background-color:transparent}` (the chrome block) is
   overridden by `.site-header{background-color:var(--surface-raised)}` (the
   system block): same specificity, later in the file. Measured on this page at
   scrollY 0, [data-stuck="false"]: the header computes rgb(255,255,255) while
   `.site-header--overlay .site-header__logo--light` is displaying the WHITE
   logo on it, so the top 114px of every hero page is a blank white bar with an
   invisible logo. Listed in handoffs; whoever owns the system block should
   scope its rule and delete this line. [data-stuck] outranks both, so the
   scroll swap is untouched. */
.site-header--overlay:not([data-stuck="true"]){background-color:transparent}

/* --- Hero brand rail ------------------------------------------------------
   Two separate faults, one cause.

   (1) The marks were not levelled. Measured bottoms 733 / 729 / 733 / 726 at
   1440 and rendered boxes 28 / 20 / 29 / 14px. Per-logo heights were being
   guessed against four files that each carry a different amount of non-ink
   payload: Sigenergy is a symbol stacked over a wordmark, Esdec carries an
   "INNOVATIVE MOUNTING SYSTEMS" strapline, MAFi a vertical "SOLAR". Centring
   four boxes whose ink sits at four different heights inside them cannot
   produce one baseline, however carefully the box heights are chosen, and at
   rail size the straplines resolved to a smear rather than to type.

   The fix is in the files, not in the CSS. Each source is re-cropped to its
   own ink bounding box, measured off the alpha channel of the original:

     growatt    crop 1929×325 at (129,154)  of 2187×672   → 404×68
     sigenergy  crop  490×59  at (5,138)    of  500×199   → 564×68   (wordmark)
     esdec      crop  529×95  at (58,14)    of  646×207   → 378×68   (wordmark)
     mafi       crop  533×200 at (0,1)      of  587×201   → 182×68

   Every file now begins and ends on ink, so ONE shared height puts all four
   cap heights and all four baselines on the same line by construction. The
   originals are kept; regenerate with ffmpeg -vf crop=…,scale=-2:68 -pix_fmt
   rgba (the pix_fmt matters: without it the alpha channel is dropped and every
   mark ships as a solid white rectangle).

   (2) The row stopped 465px short of its own container, because .hero__brands
   stretched to the full 1084px column while its content was 501px. Justifying
   the four marks across all 1084px is the wrong repair: the hero scrim
   releases to 16% alpha by x=1036, so the last two marks would land on the
   sunlit deck. The rail is given a width of its own instead, ending at x≈900
   where the H1 and the trust rail above it also end and where the scrim still
   holds, with the marks distributed across it and a hairline stating that the
   width is a decision. */
/* .hero .btn-row bar clamp(30px,3.4vw,48px) i botten. Måttet är flyttat till
   hero-blocket längst ned i filen, där de två tomrummen sätts symmetriskt. */
/* Luckan mellan "Vi bygger med" och logotyperna är LODRÄT och inte vågrät.
   .hero__brands radbryter, och karusellen tar hela bredden, så etiketten
   hamnar på en egen rad ovanför den. Den gamla regeln var gap:0 X, alltså
   radlucka noll, och de två raderna satt ihop med bara radhöjden emellan.
   Nu en riktig radlucka. Kolumnluckan står kvar för det fall etiketten och
   raden någon gång ryms bredvid varandra. */
.hero__brands{
  display:flex;align-items:center;gap:clamp(12px,1.4vw,18px) clamp(24px,2.6vw,36px);
  width:100%;max-width:724px;
  margin-top:auto;padding-top:clamp(18px,1.8vw,24px);
  border-top:1px solid rgba(255,255,255,.16);
}
.hero__brands ul{
  flex:1 1 auto;display:flex;flex-wrap:wrap;align-items:center;
  justify-content:space-between;gap:14px clamp(18px,2.2vw,30px);
}
.hero__brands-lbl{
  flex:none;font-family:var(--sans);font-size:13px;font-weight:500;
  letter-spacing:.02em;color:rgba(255,255,255,.55);
}
/* .hero__brands img in the base sheet is (0,1,1); this has to outrank it. */
/* Höjden per logotyp via --bh, med 17 px som förval. En delad höjd fungerade
   när raden bara hade breda ordmärken; med proportioner från 1,66 till 8,13
   blir den kompaktaste logotypen annars 28 px bred med oläslig text. Se
   partials/brands.html. */
.hero__brands .bmark{width:auto;height:var(--bh,17px);filter:brightness(0) invert(1);opacity:.82}

/* Brands we build with but have no white-knockout wordmark for yet. The name
   is real, so the rail is true; only the form is provisional. Set at the same
   cap height as the marks beside it so the row keeps one baseline, and marked
   with a dashed rule in the accent so it is unmistakably a slot awaiting art
   rather than a deliberate text logo. Replace with <img class="bmark"> as each
   file lands. Counted by tools/placeholders.py. */
.hero__brands .bmark-ph{
  display:inline-block;
  font-family:var(--sans);font-size:13px;font-weight:600;
  line-height:17px;letter-spacing:.01em;
  color:rgba(255,255,255,.78);
  border-bottom:1px dashed rgba(255,192,0,.65);
  padding-bottom:1px;
  white-space:nowrap;
}

/* Fifteen brands do not fit the 44px rhythm the four-logo rail was built for.
   Tighter gaps and a slightly smaller cap height keep it to two rows at 1440
   instead of three, so the rail still reads as a footnote under the hero and
   not as a second content block. */
.hero__brands ul{gap:14px 26px}

@media (max-width:900px){
  .hero__brands{
    max-width:none;flex-wrap:wrap;gap:12px 0;
    padding-top:16px;
  }
  .hero__brands-lbl{flex:1 0 100%;font-size:12px}
  .hero__brands ul{width:100%;gap:10px 14px}
  .hero__brands .bmark{height:13px}
  .hero__brands .bmark-ph{font-size:11px;line-height:13px}
}

/* --- Hero trust rail on a phone -------------------------------------------
   At 390 the rail ran y104–274 as three stacked rows with rules, and the H1
   did not start until y296: 35% of an 844px viewport spent before the buyer
   learns what the company does. Otovo's equivalent three-slot rail is a single
   40px row. Below 760 the qualifier lines go and the three labels run as one
   wrapping row, which is ~44px instead of ~170px and puts the headline back on
   the first screen. Nothing is lost: each qualifier restates its label. */
@media (max-width:760px){
  .hero__trust{
    flex-direction:row;flex-wrap:wrap;align-items:baseline;
    gap:2px 0;margin-bottom:22px;
  }
  /* span- och b-reglerna som stod här pekade på underraden och dess etikett.
     Underraderna är borttagna i markupen och etiketten är nu objektet självt,
     så typografin flyttar hit. */
  .hero__trust-item{
    display:inline-flex;flex-direction:row;align-items:baseline;
    gap:0;padding:0;border:0;
    font-size:13.5px;font-weight:500;color:rgba(255,255,255,.86);
  }
  /* Separator on the trailing edge of the item before, not the leading edge
     of the item after: the row wraps between items, and a middot that starts
     a line reads as a bullet. */
  .hero__trust-item:not(:last-child)::after{
    content:"·";margin:0 9px;color:rgba(255,255,255,.42);
  }
}

/* --- Intent router: the dead area under the cards -------------------------
   The tile reserved 52px of bottom padding for an arrow pinned to its bottom
   left, plus a 150px floor. With two short lines of content that left a hole
   inside every card and a second one under the row, and the band read as a
   grid of four half-empty boxes.

   The arrow moves to the top right, where it sits beside the label it belongs
   to instead of 60px below it, and the floor goes: four cards in a grid are
   already the same height as each other. The band also closes tighter than it
   opens, which is the rhythm every other band on the site now uses. */
/* Luften ligger på .intent-band__inner nu, och kortets vaddering är en hel
   platta via data-tile-pad. */
.intent__arrow{left:auto;right:20px;top:21px;bottom:auto;width:17px;height:17px}
@media (max-width:560px){
  .intent{padding:18px 20px}
}

/* --- The statement pair ---------------------------------------------------
   Gated on min-width, not left bare: this block lands after the base sheet, so
   an unqualified .statements{grid-template-columns:…} outranks the
   `@media (max-width:860px)` single-column rule that already exists up there.
   Scoped to .reasons as well, because .statements is not mine alone: the pair
   on produkter.html uses it too and still wants the split it was tuned for. */
@media (min-width:861px){
  .reasons > .statements{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
}
.statement--blue .link-arrow{border-color:rgba(255,255,255,.38);color:#fff}
.statement--blue .link-arrow:hover{border-color:#fff;color:#fff}
/* The methodology line under the elbil figure. Same job and same treatment as
   .swing__note directly below it, inverted for the blue ground. It has to beat
   `.statement--blue p` (0,1,1), so it is written at the same weight. */
.statement--blue .statement__note{
  margin-top:6px;font-size:14px;line-height:1.55;
  color:rgba(255,255,255,.66);text-wrap:pretty;
}
/* .ph inverts on every dark ground in the placeholder block, but that list is
   written in ground classes and .statement--blue is a card, not a ground. */
.statement--blue .ph{
  color:var(--accent);
  background-color:rgba(255,192,0,.10);
  border-color:rgba(255,192,0,.55);
}

/* --- #pris: the head ------------------------------------------------------
   The qualifying sentence used to be the second column of a .head-row, which
   is align-items:end, so it was bottom-aligned to a two-line H2: its baseline
   matched nothing, it lined up with neither the eyebrow nor the cap line, and
   it floated 146px off the heading's right edge. It reads as an ingress now,
   under the heading, the same shape the avkastning block above already uses.
   The anchor lands here from the blue intent tile and the header is 114px
   loose / 68px stuck, so the eyebrow needs the scroll margin or the reader
   arrives mid-section. */
#pris{scroll-margin-top:88px}
.pris-head{margin-bottom:8px}
.pris-head h2{margin-bottom:0}

/* --- Price cards, kept for komplettera-med-batteri.html -------------------
   index.html no longer uses .pbands / .pband: the three cards became three
   rows of one object in the home-design pass. komplettera-med-batteri.html
   still consumes them ("Reuses .pbands and .pband from index.html rather than
   inventing a second price card"), so the component stays here rather than
   being deleted out from under a page I do not own. .pbands__foot is still
   used by index.html for the grönt avdrag methodology line. */
.pbands{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px;margin-top:40px}
.pband{
  display:flex;flex-direction:column;align-items:flex-start;gap:0;
  padding:clamp(22px,2.4vw,32px);
}
.pband__hus{font-size:var(--fs-h3);font-weight:600;letter-spacing:-.022em;line-height:1.25}
.pband__kwh{margin-top:4px;font-size:15px;color:var(--ink-muted)}
/* .pband__price och .pband__lbl bar de tre prisbanden på
   komplettera-med-batteri.html. Alla tre var tomma markeringar och togs bort
   1 augusti 2026; korten står kvar utan prisrad. */
.pband__spec{margin-top:14px;font-size:14px;line-height:1.5;color:var(--ink-muted)}
.pband--ref{border-color:var(--brand-100);box-shadow:inset 0 2px 0 var(--brand)}
.pbands__foot{
  max-width:82ch;margin-top:24px;
  font-size:14px;line-height:1.6;color:var(--ink-muted);text-wrap:pretty;
}
.pbands__foot .link-arrow{margin-left:6px;font-size:14px}
@media (max-width:980px){
  .pbands{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:640px){
  .pbands{grid-template-columns:minmax(0,1fr);gap:14px}
}

/* --- Segment router: off the amber ground ---------------------------------
   The band was 1440×536 of solid --accent, the largest and loudest colour
   object on the page, with a dark blue button inside it. Amber on this site
   means "click this"; spending it on a 536px background makes every real
   button on the page compete with a wall. Svea Solar keeps its pages ~95%
   greyscale for exactly this reason and Otovo rations its peach to one control
   per screen.

   It moves to --dark, which is the one rung of the six-ground ladder this page
   did not use, and the primary button takes the amber. That also gives the
   page a dark anchor before the closer, which is what Otovo and 1KOMMA5° both
   do with their single graphite band. The eyebrow drops to --brand-300 so the
   band contains exactly one amber object.

   The crossfade goes with it. On amber the photo was masked out across
   x900–1100 and the overlap read as a muddy olive-brown band, i.e. as a
   rendering fault. On a dark ground the two halves can simply meet: a hard
   vertical edge at the media boundary, which is what every split hero in the
   benchmark set does. */
.router.section--dark .router__media::after{content:none}
.router.section--dark .router__media img{-webkit-mask-image:none;mask-image:none}
/* Frågan är sektionens ingång: den som svarar ja på den läser vidare, den som
   svarar nej ska kunna hoppa. Därför inte 14 px grått ögonbryn som på sidans
   övriga sektioner, utan satt så den drar blicken. Storleken måste ligga HÄR
   och inte på .router__eyebrow: den regeln har samma specificitet som
   .eyebrow{font-size:14px} längre ned i filen och förlorar på källordning. */
.router.section--dark .router__eyebrow{
  color:#fff;
  font-size:clamp(18px,calc(15.2px + 0.7vw),22px);font-weight:600;
  letter-spacing:-.012em;line-height:1.25;margin-bottom:12px;
}
.router.section--dark p{color:var(--dark-body)}
.router.section--dark .btn--primary{background:var(--accent);color:var(--ink)}
.router.section--dark .btn--primary:hover{background:var(--accent-lit);color:var(--ink)}
@media (max-width:900px){
  /* Höjden sätts av js/tiles.js i hela plattrader, så det gamla 16:10-måttet
     och min-height gör ingenting längre och är strukna. Skälet de skrevs av
     står kvar: en 260 px brevlåda visade en kabel och hörnet av ett skåp. En
     ruta räknad ur bildens eget förhållande kan inte hamna där. */
}
@media (max-width:640px){
  /* Below this the photo earns nothing: it is 350px of grey cabinet between
     the argument and the button. Kvar efter ombyggnaden 1 augusti: rutan är
     snyggare nu, men på en telefon är den fortfarande 350 px grått skåp mellan
     argumentet och knappen. js/tiles.js hoppar över den när den är gömd. */
  .router.section--dark .router__media{display:none}
}

/* --- Processen: the rail is not a control --------------------------------
   The amber fill was the second decorative use of the accent on this page: a
   scroll-linked progress rail in the colour reserved for things you click.
   It moves to --brand, which is what the site's own rule already says (nothing
   solid is black, bars and active states are --brand, --ink is for type).
   The critic asked for an --ink fill; that would break the rule the rest of
   the site keeps, so brand blue it is. Only the animation's end colour has to
   be restated, because the keyframe carries it. */
.home .track__step::before{background-color:var(--brand);border-color:var(--brand)}
.home .track__line::after{background-color:var(--brand)}

@media (max-width:640px){
  /* A 2px rail and a 13px dot were eating 34px of a 390px viewport, leaving
     the step body 316px at 15px, about 34 characters. On one column the four
     steps are already in order; the rail is not carrying the argument there.

     Noden är däremot kvar sedan den fick sitt stegnummer, och det är
     avsiktligt. Invändningen ovan gällde vågrätt utrymme: pluppen sköt in
     texten 34 px. Det gör den inte längre, för padding-left är 0 här och den
     numrerade brickan ligger i stället i det lodräta utrymme padding-top
     redan reserverar, ovanför bilden. Den kostar alltså ingen radbredd och
     säger vilket steg av fyra man läser, vilket är mer värt på en skärm där
     man ser ett steg i taget än på en där man ser alla fyra.
     Regeln som dolde den låg kvar här och blev överskriven av
     comment-round-2 på källordning. Den är borttagen i stället för att
     lämnas som en regel utan verkan. */
  .home .track__line{display:none}
  .home .track__step{padding-left:0}
  .home .track{--gap:32px}
}

/* --- Tap targets on a phone -----------------------------------------------
   Measured at 390: the utility-bar phone link 103×21, the Google rating
   128×21, every footer nav link 17px tall, the inline arrow links 24–25px.
   The phone number is this company's second conversion path and it was a 21px
   target on a phone.

   Two techniques, chosen per case. Where a row can simply become 44px without
   moving anything else (the utility bar, the footer nav) it does. Where the
   visual rhythm has to survive (inline .link-arrow inside prose) the hit area
   is grown with a transparent pseudo-element instead, so nothing moves and
   nothing overlaps: an inline arrow link never has another control within
   22px of it. Deliberately NOT done with negative margins on the footer list,
   because 44px boxes 28px apart overlap by 16px and a tap in the overlap goes
   to whichever link paints last. */
@media (max-width:760px){
  .utility__inner{min-height:44px}
  /* Taket för det genomskinliga huvudets hopfällning måste följa med när raden
     växer, annars klipper overflow:hidden bort skillnaden. Se noten vid
     .site-header--overlay .utility. */
  .site-header--overlay{--utility-h:44px}
  .utility__rating,.utility__tel{min-height:44px}
  .site-footer .site-footer__col ul{gap:0}
  .site-footer .site-footer__col li a{display:flex;align-items:center;min-height:44px}
  .site-footer .site-footer__contact li{min-height:44px;align-items:center}
  .link-arrow{position:relative}
  .link-arrow::after{
    content:"";position:absolute;left:-6px;right:-6px;top:50%;
    height:44px;transform:translateY(-50%);
  }
}

/* --- Reviewbar: the link matches every other mid-page ask on the site ----- */
.reviewbar__score .link-arrow{align-self:flex-start;margin-top:6px}

/* --- Remaining phone targets, measured not guessed --------------------------
   Re-measured at 390 after the block above: the footer legal links were still
   21px, the two social icons 37px, and the hero trust labels 26px now that
   they run as one row. None of the three can take a min-height without moving
   something, so all three grow a transparent hit area instead. The nearest
   neighbour in each case is more than 22px away, so no two boxes overlap. */
@media (max-width:760px){
  .site-footer__legal a{position:relative}
  .site-footer__legal a::after{
    content:"";position:absolute;left:-8px;right:-8px;top:50%;
    height:44px;transform:translateY(-50%);
  }
  .site-footer .social--icon{min-width:44px;min-height:44px}
  a.hero__trust-item{position:relative}
  a.hero__trust-item::before{
    content:"";position:absolute;left:-6px;right:-6px;top:50%;
    height:44px;transform:translateY(-50%);
  }
}

/* --- Hero photograph: grade, not replace ----------------------------------
   Beside the other four heroes this is the greenest and the flattest: the
   trees on the right push the whole frame toward cyan-green and the roof loses
   its edge. A light grade puts the panels back and takes the cast off, without
   touching the file (it is also the og:image) and without the site growing a
   second treatment for the same picture. Deliberately small: saturation down
   one tenth and contrast up six per cent is the difference between a snapshot
   and a photograph, and anything past that starts to look processed. */
.hero__media img{filter:saturate(.9) contrast(1.06)}
/* ==== end: home ==== */

/* ==== added: produkter ==== */
/* ==========================================================================
   produkter.html
   A datasheet/warranty row the page did not have, an optical fix for the
   twelve product frames, sharper video facades, and the tidy-up left behind by
   removing the nätfrekvens gauge.

   Second pass (design review): the display figure is set in the sans, the four
   .head-row notes all sit on the heading's baseline, the two label/content
   splits carry their own first paragraph so no column is two thirds air, the
   scrolling tab rail admits it scrolls on a phone, the brand grid closes its
   own row, and the amber bloom is pulled back off this page's two teal grounds.
   ========================================================================== */

/* --- Datasheet rows -------------------------------------------------------
   Name / what it means / our value. Used twice: the battery comparison rows on
   the Batterier tab and the warranty table in its own band. Deliberately the
   same rhythm as .specs above it (a rule in --brand on top, hairlines between,
   values on the right) so the page reads as one table system rather than two.

   The middle column is the reason this beats 1KOMMA5°'s spec block: they print
   the value and leave the reader to know what it is for. The row is worth
   reading even while the value is still a placeholder. */
.dsheet{margin:0;border-top:1px solid var(--brand)}
.dsheet__row{
  display:grid;
  grid-template-columns:minmax(0,.72fr) minmax(0,2fr);
  gap:10px 32px;align-items:baseline;
  padding:22px 0;border-bottom:1px solid var(--hair);
}
.dsheet__row dt{font-size:17px;font-weight:600;letter-spacing:-.015em;color:var(--ink)}
.dsheet__row dd{margin:0}
.dsheet__why{color:var(--ink-muted);font-size:16px;line-height:1.55;text-wrap:pretty}
/* .dsheet__val (värdekolumnen) togs bort 1 augusti 2026 tillsammans med
   platshållarna den bar. Båda tabellerna är tvåspaltiga nu: raden och vad den
   betyder. Värdena står i offerten i stället, vilket noten under varje tabell
   säger. Ägarblocket lämnas tomt så en ny körning inte återuppstår reglerna. */
/* The header row is a column key, not data: same grid, and the list's own
   --brand rule underneath does the separating. */
.dsheet__head{
  display:grid;
  grid-template-columns:minmax(0,.72fr) minmax(0,2fr);
  gap:32px;padding-bottom:10px;
}
.dsheet__head span{
  font-family:var(--sans);font-size:var(--fs-mono-sm);font-weight:500;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-faint);
}
@media (max-width:800px){
  /* Same collapse as .specs: label and value stay on one line and the
     explanation drops under both, so the value column is never squeezed to
     three characters per line. */
  .dsheet__row{grid-template-columns:minmax(0,1fr);gap:6px;padding:20px 0}
  .dsheet__head{display:none}
}

/* --- The display figure ---------------------------------------------------
   Hela .statblock-familjen (__fig, __unit, __label, --pending) togs bort
   1 augusti 2026. Den hade en enda användning på hela sajten,
   modulverkningsgraden på produkter.html, och den var en tom markering.
   Behövs en sifferruta igen: sätt den i sans med tabular-nums och enheten i en
   egen span, aldrig i mono. En monospace-advance ger varje tecken samma cell,
   så "22,8 %" kom ut som "2 2 , 8   %" i sidans största grad. */

/* --- Head rows sit on the heading's baseline ------------------------------
   Three of the four .head-row blocks on this page carried an inline
   align-items:baseline and the fourth did not, so on the Batterier tab the note
   dropped to the foot of a 197px block and floated beside the lede, aligned to
   nothing. One class now does it for all four, and the inline styles are gone.
   The flex baseline is taken from each column's first line, which is what puts
   the note level with the heading rather than with the block. */
.head-row--base{align-items:baseline}
@media (max-width:760px){.head-row--base{align-items:flex-start}}

/* --- Intro splits carry their own first paragraph -------------------------
   The system-wide fix for the label/content split is align-items:center, which
   splits the void above and below the heading instead of leaving it all
   underneath. On this page's two intro blocks that was not enough: an eyebrow
   plus a heading is 116px against 309px and 330px of prose, so the heading sat
   in a column that was two thirds air whichever way it was aligned.

   The markup change is the real fix (the left column now ends in the first
   paragraph). This rule only stops the centring from fighting it: with the two
   columns within ~60px of each other, top alignment reads as a pair of columns
   and centring reads as a mistake. */
@media (min-width:981px){
  .split--intro{align-items:start}
}

/* --- The tab rail admits it scrolls --------------------------------------
   At 390 the four primary categories are ~470px of labels in a 342px rail, so
   "Tak och montage" was cut at the viewport edge with no fade, no shadow and no
   arrow: a phone buyer had no way of knowing the fourth category existed. The
   labels are the product categories and shortening them costs more than it
   saves, so the rail keeps scrolling and says so.

   The mask is fixed to the element box, not to the scrolled content, so the
   right edge always fades; scroll-padding-inline-end keeps the last label clear
   of that 44px once you have scrolled to the end, so nothing is ever
   permanently dimmed. Snapping makes the gesture land on a label rather than
   halfway through one. */
@media (max-width:600px){
  .page-produkter .tabs{
    padding-right:44px;
    scroll-snap-type:x proximity;
    scroll-padding-inline-end:44px;
    -webkit-mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 44px),transparent 100%);
    mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 44px),transparent 100%);
  }
  .page-produkter .tabs .tab{scroll-snap-align:start}
}

/* --- Product frames: equal optical weight ---------------------------------
   ASSETS.md is right that the twelve product shots are framed with different
   amounts of whitespace, and wrong that nothing can be done about it in CSS.
   Measured with a canvas pass over every file, the product occupies between
   45% and 100% of its own file height, so the rendered products ran 91px to
   202px tall inside identical 230px frames: worse than a 2:1 spread.

   Each <img> carries --trim, the measured fraction of the file its product
   actually fills, and the frame sizes the image so the PRODUCT lands at
   --optical of the frame's inner height whatever the file does. The surplus is
   that file's own whitespace and the frame clips it, which is the hand-trim
   ASSETS.md asks for, done at render time.

   Two of the eleven were outside what the cap could absorb and were fixed in
   the files instead, which is where a framing problem belongs:
   batteri-sungrow.webp was re-cropped 455x455 → 300x300 around its own product
   (--trim .45 → .68, product ink 119px → 161px, level with the BYD card beside
   it), and vaxelriktare-solis.webp had a --trim of .71 against a measured .822,
   which scaled it to 228px inside a 202px frame and clipped the cable ends off
   the bottom. All eleven now land between 156 and 162px.

   If a photo is replaced, re-measure its --trim or the frame will scale it
   wrongly. */
.media-slot--photo{overflow:hidden}
.media-slot--photo img{
  --trim:1;
  --optical:.80;
  width:auto;max-width:none;max-height:none;
  /* Capped at 1.32x the inner height, which now only ever binds on the Growatt
     inverter (measured trim .588). Uncapped it would scale to 275px in a 202px
     frame and lose its base at the hairline. */
  height:min(
    calc((var(--slot-h) - var(--slot-pad) * 2) * var(--optical) / var(--trim)),
    calc((var(--slot-h) - var(--slot-pad) * 2) * 1.32)
  );
}
/* One of the twelve is not a cut-out: the Sigenergy inverter shot is a studio
   photograph on a light grey ground. Scaled to the same optical size it sat as
   a bare grey rectangle inside a white frame, which reads as a failed cut-out.
   A hairline and a small radius say "photograph" instead. */
.media-slot--inset img{border:1px solid var(--hair);border-radius:var(--radius-sm)}

/* --- The brand grid closes its own row ------------------------------------
   Five inverter cards in a three-column grid left the whole bottom-right cell
   empty: a ~345x405px hole closing the section. The sixth cell answers the
   question the grid raises ("so which one do I get?") and is deliberately not
   a card: no white ground, no border, so it never reads as a twelfth product.
   The one-line .note that used to carry this argument below the grid is gone
   with it. */
.brand--note{background:none;border:0;border-radius:0}
.brand--note .brand__body{
  flex:1 1 auto;justify-content:center;gap:10px;
  padding:4px 0 4px 4px;
}
.brand--note .h4{max-width:20ch}
.brand--note .muted{font-size:16px;line-height:1.55}
.brand--note .link-arrow{margin-top:4px;align-self:flex-start}
@media (max-width:600px){
  .brand--note .brand__body{padding:4px 0}
}

/* --- One installation, shown big ------------------------------------------
   The photograph is 3:4 and was forced to 4:5, so at 1440 it stood 599px tall
   against a 277px caption, both vertically centred: 161px of dead ground above
   and below the text, i.e. 54% of the right half of a full-bleed block empty.
   The subject (inverter, battery, conduit) spans about 72% of the file's
   height, so a square crop loses none of it and takes 135px off the column,
   and the caption has been given the third paragraph the block had room for. */
.install--square .install__media img{aspect-ratio:1/1;object-position:center 44%}
.install--square .install__body .note{margin-top:2px}
@media (max-width:860px){
  .install--square .install__media img{aspect-ratio:3/2}
}

/* --- Tak och montage: the figure and the odd video cell -------------------
   Two of the four sub-tabs have one video, not two, and left half a row empty.
   The cell states what is true of every roof type instead, which is also the
   thing the four videos have in common. */
/* No margin reset here: figure is already margin:0 in the base sheet, and a
   `margin:0` at this point in the file would out-rank the .mb-56 utility the
   block carries in the markup. */
/* The photograph is 16:9 and .shot--wide is 16:9, so it is shown whole: any
   tighter crop cut the lower panel row in half, and the row is the evidence.

   Bilden och bildtexten ligger sida vid sida sedan videosektionen flyttade
   över den. Två skäl. Den satt tidigare direkt mot panelernas underkant utan
   en enda pixel emellan, och den var 1124 px bred och nära 700 px hög, alltså
   sektionens största objekt placerat sist där det bara var ett avslut. I två
   spalter blir fotot ungefär 640 px brett och 360 högt, vilket är i nivå med
   videorutan ovanför i stället för dubbelt så stort, och bildtexten står
   bredvid det den beskriver i stället för under.

   Beskärningen är oförändrad: hela 16:9 syns fortfarande, bilden är bara
   mindre. Att klippa den hade tagit bort panelraden, som är hela poängen. */
.prod-fig{
  display:grid;grid-template-columns:minmax(0,1.4fr) minmax(0,1fr);
  gap:clamp(24px,2.6vw,40px);align-items:center;
  margin-top:clamp(44px,4.4vw,68px);
}
.prod-fig__cap{
  max-width:46ch;margin-top:0;
  font-family:var(--sans);font-size:15px;line-height:1.6;color:var(--ink-faint);
  text-wrap:pretty;
}
@media (max-width:820px){
  /* Under 820 blir fotot smalare än videorutan bredvid det stod under, och
     bildtexten får under 20 tecken. Då är en spalt rätt. */
  .prod-fig{grid-template-columns:minmax(0,1fr);gap:16px}
  .prod-fig__cap{max-width:70ch}
}
.video-aside{
  display:flex;flex-direction:column;gap:12px;justify-content:center;
  padding:4px 0 4px 4px;
}
.video-aside .muted{font-size:16px;line-height:1.6;max-width:44ch}
.video-aside .note{max-width:44ch}
@media (max-width:700px){.video-aside{padding:0}}

/* --- Video facades --------------------------------------------------------
   The thumbnails were YouTube's hqdefault, which is a 480x360 4:3 frame with
   the 16:9 still letterboxed inside it. The scale(1.35) existed to push those
   black bars outside the card, at the cost of cropping the picture and
   upscaling a 480px file to ~508px. All six videos have maxresdefault, which
   is 1280x720 and already 16:9, so the frame now shows the whole still, sharp,
   with no transform at all. */
.video-card__play img{transform:none}

/* --- Supplier row ---------------------------------------------------------
   The masked Mafi wordmark was filled with --brand, so it was the one
   saturated logo in a row of greyscaled ones and read at twice the weight of
   the Esdec mark beside it. Same neutral, same presence, still not black. */
.supplier__mark{background-color:var(--ink-muted)}
/* The four supplier marks were a 91px PNG, an 87px PNG, a 70px PNG and an SVG
   that turned out to be a 262px PNG in base64: all of them soft on a 2x screen,
   and these are the page's only third-party trust marks. They now point at the
   2x/3x WebP copies. Green Hero also gets a taller cap, because it is the one
   stacked two-line wordmark in the set and a 28px box left each line about
   11px tall, which read as a smudge rather than as a logo. */
/* Regeln för <img src=greenhero> är borttagen: märket är en mask nu,
   se .supplier__mark--greenhero. */

/* --- Amber, reserved for the thing you click ------------------------------
   The site-wide glow is amber at .22, which is right on the mid blues but goes
   olive over the two deepest teals: it is visible as a green bloom in the top
   left of the Prisgaranti card and, worse, straight behind the amber "Boka
   gratis analys" button in the closer, so the one functional use of --accent on
   the page sits inside a decorative one. Pulled back on this page only, since
   the token is shared. Svea keeps its pages ~95% greyscale for exactly this
   reason: one saturated thing per screen, and it is the button. */
.page-produkter .closer{--glow-a:.10;--glow-x:22%}
.page-produkter .statement--blue{--glow-a:.12}

/* --- Brand cards on a phone -----------------------------------------------
   Below 600 the grid drops to one column, which gave a 320px-wide frame to a
   product photographed portrait: a 110px object floating in the middle of a
   full-width white box, six times down the page. Laid on its side the frame is
   a fixed 128px rail and the description gets the rest, so the card reads as a
   product row rather than as a mostly empty panel. */
@media (max-width:600px){
  .brand{flex-direction:row;align-items:stretch}
  .brand .media-slot{
    --slot-h:132px;--slot-pad:10px;
    flex:none;width:128px;height:auto;
    border-bottom:0;border-right:1px solid var(--hair);
  }
  .brand .media-slot--photo img{
    height:min(calc(104px * var(--optical) / var(--trim)),calc(104px * 1.32));
  }
  .brand__body{flex:1 1 auto;padding:18px 18px 20px}
  .brand--note{flex-direction:column}
}
/* ==== end: produkter ==== */
/* ==== added: steg-anim-outro ==== */
/* (retired: the full-viewport animation section was replaced by the in-frame
   version; owner block kept empty so a re-run cannot resurrect old rules) */
/* ==== end: steg-anim-outro ==== */

/* ==== added: steg-anim ==== */
/* --- Montaget i 3D, inside the journey frame (steg-for-steg.html) ----------
   The canvas lives in .journey__frame, stacked under the two step photos.
   The photos fade via .on for steps one and two; from Installation on, no
   photo is .on and the canvas shows through. Scroll progress comes from the
   tall [data-solar-progress] step in the text column (see js/steg-anim.js),
   so the sticky frame builds the model while the phase list scrolls past. */

/* The stage carries its own sky so the transparent canvas reads as a place,
   not a void, in both of its homes (frame on desktop, slot on phones). The
   sky is the site's cool tints, background-color + image per the ground
   rule. */
/* The canvas is transparent, so this gradient IS the sky. It is graded to the
   same dusk value the component holds the scene at, otherwise the model reads
   as a dimmed object cut out against a bright daylight sky. Deep at the
   horizon, lifting toward the zenith so the roofline still separates. */
.janim__stage{
  background-color:#9FBACB;
  background-image:radial-gradient(130% 85% at 50% 6%,
    #DCE9F0 0%, #B9CFDD 34%, #93B0C2 66%, #6E8FA5 100%);
}
.journey__frame .janim__stage{position:absolute;inset:0}
.janim__stage canvas{display:block;width:100%;height:100%;outline:none}

/* Phase list inside the Installation step.
   It used to be seven blocks of 60vh scrolling past a sticky frame, all of
   them legible at once, the current one picked out by an accent rule. On
   comment 1 August 2026 it holds still and cuts instead:

     .janim__track   pure scroll distance, nothing is drawn in it
     .janim__pin     what the visitor sees, held in place while that
                     distance is spent
     .janim__phase   seven cards stacked on the same spot inside the pin;
                     only .is-active is visible

   Which card shows is decided by .is-active and by nothing else, and
   js/solar-scroll.js sets that from the same progress number that drives the
   camera. So the text cut lands exactly on the phase change in the model. The
   old layout could not promise that: card position came from DOM height and
   phase state came from the j/6 windows, and the two only agreed roughly.

   Hiding the six inactive cards reverses the Analysen rule (never dim most of
   a section to mark one part of it). It is a different situation: this is one
   card at a time in a fixed frame, not three quarters of a visible list
   greyed out, and nothing is dimmed, it is simply not on stage yet.

   The track is the scroll budget for the whole build. 420vh over seven
   windows gives the establishing shot about 35vh and each of the six working
   phases about 70vh, which is the "tillräckligt skroll" before each cut. */
.janim__track{margin-top:34px;height:420vh}
.janim__pin{
  position:sticky;
  /* The card is anchored by its top, not its middle, so the heading lands on
     the same line in every phase and the change reads as a cut rather than as
     a block resettling. The offset is therefore measured to put the BODY of a
     full card (heading, sentence, product link) on the frame's centre line;
     the two short cards then sit a little high, which is the right way round.
     The frame is square, about half the viewport wide, so its centre is near
     50vh + 30px. */
  top:max(24px,calc(50vh - 115px));
}
.janim__phases{position:relative;min-height:280px}
.janim__phase{
  position:absolute;top:0;left:0;right:0;
  display:flex;flex-direction:column;
  padding:4px 0 4px 22px;
  border-left:2px solid var(--hair);
  opacity:0;visibility:hidden;
  transition:opacity .26s ease,visibility 0s linear .26s;
}
.janim__phase.is-active{
  opacity:1;visibility:visible;
  border-left-color:var(--accent);
  transition:opacity .26s ease,visibility 0s linear 0s;
}
/* One card on stage at a time, so it is set at full reading weight rather
   than lifted out of a row of dimmer neighbours. Bigger too: at 17/15 it was
   sized to be skimmed in a list, and it is now the only text in the frame. */
.janim__phase h4{
  font-size:22px;font-weight:600;letter-spacing:-.015em;
  color:var(--ink);margin-bottom:8px;
}
.janim__phase p{font-size:17px;line-height:1.55;color:var(--ink-muted);max-width:46ch}

/* Phones: the sticky frame is display:none below 900px, so js/steg-anim.js
   re-parents the stage into [data-solar-slot]. That slot now sits inside the
   pin, so the model and the sentence about it are held as one block; before,
   the slot was sticky on its own and the phase text ran underneath it. */
.janim__slot{display:none}
@media (max-width:900px){
  .janim__track{margin-top:26px;height:340vh}
  /* Stage plus card is about 510px of the ~844px phone. Pinned at the very
     top that left a third of the screen empty under it, so it is centred in
     the viewport instead, and falls back to the old 12px on short screens. */
  .janim__pin{top:max(12px,calc(50vh - 262px));z-index:2}
  .janim__slot{display:block}
  .janim__slot .janim__stage{
    position:relative;aspect-ratio:4/3;
    /* A sticky box taller than the viewport does not hold, it just scrolls,
       and at 900px wide the 4:3 stage alone is 615px. Capping it against the
       viewport keeps stage plus card inside the screen so the pin can do its
       job. The component re-solves the camera from the stage's real box on
       resize, so a shorter frame is framed correctly rather than cropped. */
    max-height:44vh;
    border:1px solid var(--hair);border-radius:var(--radius);overflow:hidden;
  }
  .janim__phases{margin-top:18px;min-height:230px}
  .janim__phase h4{font-size:20px}
  .janim__phase p{font-size:16px}
}
/* Phone on its side, or a small window at the phone breakpoint. Give the card
   the height back that the reserved box was holding: the cards are absolutely
   positioned, so they still draw, the pin box is simply no larger than what it
   must reserve. */
@media (max-width:900px) and (max-height:640px){
  .janim__pin{top:8px}
  .janim__phases{margin-top:12px;min-height:0}
}

/* No scripting: the .js class never lands, the widget never mounts and
   .is-active is never set. Without these the seven cards would stay hidden
   forever, so the whole mechanism unwinds back into a plain readable list. */
html:not(.js) .janim__stage{display:none}
html:not(.js) .janim__track{height:auto}
html:not(.js) .janim__pin{position:static}
html:not(.js) .janim__phases{
  min-height:0;display:flex;flex-direction:column;gap:26px;
}
html:not(.js) .janim__phase{
  position:static;opacity:1;visibility:visible;
}

/* Scripting but no WebGL: the component sets [data-solar-failed] on the
   journey root. Swap the canvas for the static montage photo and collapse
   the scroll machinery; the step reads as it did before the animation.
   The phase cards unwind the same way as in the no-JS case: the mount
   returned null, so nothing will ever set .is-active. */
.janim__fallback{display:none;margin:0}
[data-solar-failed] .janim__stage canvas{display:none}
[data-solar-failed] .janim__fallback{display:block;position:absolute;inset:0}
[data-solar-failed] .janim__fallback img{width:100%;height:100%;object-fit:cover}
[data-solar-failed] .janim__track{height:auto}
[data-solar-failed] .janim__pin{position:static}
[data-solar-failed] .janim__phases{
  min-height:0;display:flex;flex-direction:column;gap:26px;
}
[data-solar-failed] .janim__phase{
  position:static;opacity:1;visibility:visible;
}
@media (max-width:900px){
  [data-solar-failed] .janim__fallback{position:relative}
}
/* .journey__frame img sets opacity:0 awaiting .on; the fallback photo is
   inside the frame too, so it inherited invisibility. It is governed by
   [data-solar-failed] alone. */
[data-solar-failed] .janim__fallback img{opacity:1;transform:none}

/* --- Feature 2: the canvas must sit UNDER the step photographs ------------
   Both the photos and the stage are position:absolute inside
   .journey__frame, and the stage comes later in the DOM, so with z-index
   auto it painted on top and the Analys & offert photo was invisible behind
   a 3D house. The photos own the frame; the canvas is what shows through
   when none of them is .on. */
.journey__frame .janim__stage{z-index:0}
.journey__frame img{z-index:1}
.journey__cap{z-index:3}

/* --- Product links, one per montage phase --------------------------------
   A thumbnail of the actual part, linking into produkter.html. The anchors
   name a tabpanel, and initTabs() in js/site.js opens the tab a hash names,
   so these land on an open panel rather than a hidden one. */
.janim__prod{
  display:flex;align-items:center;gap:14px;
  margin-top:18px;padding:8px 14px 8px 8px;
  width:fit-content;max-width:100%;
  border:1px solid var(--hair);border-radius:calc(var(--radius) + 2px);
  background-color:var(--surface-raised);
  text-decoration:none;
  transition:border-color .2s ease,background-color .2s ease;
}
.janim__prod:hover{border-color:var(--brand-300);background-color:var(--brand-25)}
.janim__prod:hover .janim__prod-arrow{transform:translate(2px,-2px)}
.janim__prod img{
  width:56px;height:56px;flex:none;
  border-radius:var(--radius-sm);object-fit:cover;
  background-color:var(--surface-sunken);
}
.janim__prod-txt{display:flex;flex-direction:column;gap:1px;min-width:0}
.janim__prod-txt b{font-size:15px;font-weight:600;letter-spacing:-.01em;color:var(--ink)}
.janim__prod-txt span{font-size:13px;line-height:1.4;color:var(--ink-faint)}
.janim__prod-arrow{
  width:14px;height:14px;flex:none;margin-left:4px;
  color:var(--brand);transition:transform .2s ease;
}
/* Inactive phases keep the link readable but quiet, so the active phase is
   still the one the eye lands on. Never hidden: the Analysen rule. */
/* The inactive cards used to be dimmed here too; they are off stage now, so
   the product link is simply at full weight whenever it can be seen. */
.janim__prod{transition:border-color .2s ease,background-color .2s ease}
@media (max-width:900px){
  .janim__prod{margin-top:14px;gap:11px}
  .janim__prod img{width:48px;height:48px}
}
/* ==== end: steg-anim ==== */

/* ==== added: steg-anim-vignette ==== */
/* --- Cinematic vignette over the montage stage -----------------------------
   A deep blue-night veil at the frame edges. The component pulls the scene's
   environment toward the same value during the build phases (the "focus"
   system in js/solar-scroll.js); this closes the frame around it. Painted on
   a pseudo-element so it costs nothing and never touches the canvas.

   The veil is constant across the whole sequence, matching applyFocus() in
   the component, which now holds its dim at full strength from the
   establishing shot to the pull-back. It was previously keyed to
   [data-solar-active] and dropped to .22 for intro and done; that made those
   two moments read as a brighter illustration bolted onto the same sequence.
   One value, no phase switching, so nothing to keep in sync. */
.janim__stage::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(118% 92% at 50% 42%,
    transparent 52%, rgba(2,26,37,.38) 86%, rgba(2,26,37,.58) 100%);
  opacity:.82;
}
/* The caption gradient sits above the vignette, so the label stays clean. */
.journey__cap{z-index:3}
/* No WebGL: the static photo replaces the canvas, and a heavy vignette over
   a photograph reads as a mistake. Keep the veil off entirely. */
[data-solar-failed] .janim__stage::after{content:none}
/* ==== end: steg-anim-vignette ==== */

/* ==== added: comment-round/reasons ==== */
/* ==== added: reasons-section ==== */
/* --- "Ingen anläggning är som en annan": första raden närmare rubriken -----
   Rubriken "Produkterna kommer sedan." är andra halvan av avsnittets budskap,
   men satt i en rad som är centrerad mot en 385 px hög bild började den
   ungefär 150 px under ingressen. Två ändringar, ingen av dem i typografin:
   mindre luft över den första artikeln, och texten toppställd mot bilden i
   stället för centrerad, så rubriken börjar där bilden börjar. Bara första
   raden: rad två har kortare text och mår bra av att ligga centrerad. */
.reasons > .reason:first-child{padding-top:32px}
@media (min-width:861px){
  .reasons > .reason:first-child .reason__body{align-self:start}
}

/* --- "Vad du får" från banner till informationsruta ------------------------
   Rutan bar fyra grå enradare i ett tvåkolumnersrutnät med två knappar under,
   alltså formen på en kampanjbanner, medan innehållet är det som avgör om
   någon vågar ta nästa steg. Den är nu en ruta i två spalter: rubrik och en
   knapp i den smala, punkterna i den breda. Punkterna har fått en egen
   etikett i versaler, satt som en liten spärrad ledtext och inte som skrik,
   plus en hel mening var i stället för en kolonfras. */
.getpanel{
  display:grid;grid-template-columns:minmax(0,.82fr) minmax(0,1.18fr);
  gap:clamp(28px,3.4vw,56px);align-items:start;
  margin-top:clamp(32px,3.6vw,48px);
  padding:clamp(28px,3vw,48px);
}
.getpanel .inline-check{grid-template-columns:minmax(0,1fr);gap:22px}
.getpanel .inline-check li{align-items:start;gap:14px}
.getpanel .inline-check svg{transform:translateY(4px)}
.getpanel .inline-check b{
  display:block;margin-bottom:2px;
  font-family:var(--sans);font-size:var(--fs-mono-sm);font-weight:600;
  letter-spacing:.11em;text-transform:uppercase;color:var(--ink);
}
.getpanel .inline-check span{text-wrap:pretty}
@media (max-width:860px){
  .getpanel{grid-template-columns:minmax(0,1fr);gap:26px}
}
/* Knappen är radens enda, så den ska inte sträcka sig över hela spalten på
   telefon heller: .btn-row lägger annars width:100% på ensamma knappar. */
@media (max-width:640px){
  .getpanel .btn-row{align-items:flex-start}
  .getpanel .btn-row .btn{width:auto}
}
/* ==== end: reasons-section ==== */
/* ==== end: comment-round/reasons ==== */

/* ==== added: comment-round/avkast ==== */
/* ==========================================================================
   index.html, avkastningsblocket (.swing) — delade staplar
   Owner key: avkast. Allt här är scopat till .swing, som bara finns på
   startsidan; .meter delas med lonsamhet.html och rörs inte.

   Blocket ska visa tre saker utan legend: vad elen kostar i dag, hur mycket av
   den kostnaden som försvinner, och vad anläggningen tjänar in. Tre grepp gör
   det:

   1. En skala för alla tre raderna. Hela banan är 28 000 kr, så intäktsraden
      ritas halvlång och läsaren ser storleksförhållandet utan att jämföra
      siffror.
   2. Varje del får sin text rakt under sig. Nycklarna ligger i ett rutnät med
      exakt samma spår som staplarna (--a och --b sätts en gång per rad och
      styr både segmentbredd och kolumnbredd), och varje nyckel har ett streck
      i sin egen färg i vänsterkanten. Ingen ruta att slå upp någon annanstans.
   3. Färgen bär betydelse, inte dekoration, och bara en enda: grått är pengar
      som lämnar huset, blått är pengar på husets sida. Därför är hela översta
      raden grå, därför behåller "kvar att betala" exakt samma grå en rad ner
      (det är samma sorts pengar, bara mindre av dem), och därför är allt annat
      blått. Att den gråa delen krymper från hela banan till fyra tiondelar är
      argumentet, och det syns med hopknipna ögon.
      Blått kommer ur --brand-skalan: --brand för det som sparas, --brand-500
      och --brand-300 för de två intäktsposterna, som därmed läses som ett par.
      Ingen gul någonstans: gult betyder "klicka här" på den här sajten och
      staplarna går inte att klicka på.

   Färgen är aldrig ensam bärare av något. Varje segment har sitt värde
   utskrivet i text intill, så blocket läses lika bra i gråskala.
   ========================================================================== */

/* Stapeln plus dess nycklar är ett objekt och tar hela radens bredd, på samma
   plats i rutnätet som .meter ensam hade förut. */
.swing__row .swing__bar{grid-column:1/-1}

/* Två segment i samma bana. Mellanrummet är 3 px, alltså precis så mycket att
   delningen syns utan att bli en egen linje. Segmenten får krympa (default
   flex-shrink) så att en rad som summerar till 100 % inte skjuter ut ur banan.
   Färgerna sätts en gång per rad, på .swing__bar, för att segment och nyckel
   ska vara omöjliga att sätta i otakt. */
.meter--split{display:flex;gap:3px}
.meter--split>span:first-child{width:var(--a);background-color:var(--c1)}
.meter--split>span:nth-child(2){width:var(--b);background-color:var(--c2)}
/* Andra segmentet startar där det första slutar, så det växer också en gnutta
   senare. .meter span animeras redan med scaleX från vänsterkanten. */
.meter--split>span:nth-child(2){animation-delay:.14s}

/* Nyckelraden ärver storlek, färg och toppmarginal från .meter+p, som redan är
   sajtens roll för bildtext under en stapel. Här läggs bara spåren till. */
.swing__keys{display:grid;grid-template-columns:var(--a) minmax(0,1fr)}
.swing__key{
  display:flex;flex-direction:column;align-items:flex-start;gap:3px;
  padding-left:10px;padding-right:16px;
  border-left:2px solid var(--hair-strong);
}
.swing__key:first-child{border-left-color:var(--c1)}
.swing__key:nth-child(2){border-left-color:var(--c2)}
/* Talet under sitt eget namn: kolumnen är ibland bara en fjärdedel av banan,
   och två rader håller ihop bättre där än en rad som bryter mitt i en summa. */
.swing__key b{
  font-weight:600;color:var(--ink);letter-spacing:-.01em;
  font-variant-numeric:tabular-nums;
}

/* Vad de två intäktsposterna faktiskt är. 15/400 --ink-muted, samma roll som
   annan förklarande text inne i ett objekt på startsidan. */
.swing__gloss{
  max-width:60ch;margin-top:-4px;
  font-size:15px;line-height:1.6;color:var(--ink-muted);text-wrap:pretty;
}

@media (max-width:600px){
  /* En fjärdedels bana är ~85 px här, och "Frekvensreglering" får inte plats
     på 85 px i någon grad som går att läsa. Nycklarna staplas därför, och
     färgstrecket i vänsterkanten är kvar: det är samma streck som pekade rakt
     upp i sitt segment på bredare skärmar, så kopplingen överlever. */
  .swing__keys{grid-template-columns:minmax(0,1fr);gap:8px}
  .swing__key{flex-direction:row;align-items:baseline;gap:8px;padding-right:0}
}
/* ==== end: comment-round/avkast ==== */

/* ==== added: comment-round/pris ==== */
/* --- #pris: hela spannet i stället för tre tomma prisband -----------------
   Sektionen var tre prisband per husstorlek där varje prisruta innehöll en
   tom ANGE-markering. Banden är borta och med dem .plist, .plist__row,
   .plist__hus, .plist__price, .plist__from och .plist__more, som ingen
   annan sida använder. Reglerna för dem står kvar längre upp i filen tills
   någon städar den; de matchar ingenting längre. .pbands / .pband rörs inte
   alls, de tillhör komplettera-med-batteri.html.

   Det som ersätter dem är ett objekt med två halvor: siffran till vänster,
   telefonnumret till höger. Papper och blått hårstreck är samma botten som
   .plist hade, så sektionens egen botten (--brand-50) ser ut som förut och
   sidans sex bottnar stegar vidare orörda.

   Siffran tar samma display-figure-roll som .swing__fig och .swing__sum-fig
   i avkastningsblocket rakt ovanför (sans, 600, -.035em, tabular-nums,
   --brand), men ett steg mindre än --fs-num-lg: strängen är ett spann på
   sjutton tecken, inte ett tal på nio, och i en halv panelbredd tar 48 px
   slut innan raden gör det. Egen kurva av samma skäl, med basvärde i px och
   en flack vw-term precis som skalstegen högst upp i filen: container-
   bredden stannar vid 1180 px, så spalten är 380 till 436 px hela vägen
   från 1024 och uppåt och siffran ska stå kvar runt 40 px i hela det
   spannet i stället för att rasa med bredden. Mätt: 240 px bläck vid 390,
   298 vid 1024, 310 vid 1440. Talet står på en rad på varje bredd. */
.home .pspan{
  display:grid;grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);
  gap:32px clamp(40px,4.4vw,64px);align-items:start;
  margin-top:40px;padding:clamp(28px,3vw,44px);
  background-color:var(--surface-raised);
  border:1px solid var(--brand-100);
  border-radius:var(--radius);
}
/* .home .pspan__fig är borttagen. Den satte display-siffran 80 000–600 000 kr,
   som i andra kommentarsvändan ersattes av tre utskrivna nivåer (.ptiers) med
   spannet nedflyttat till .pspan__note. Ingen sida använder klassen längre. */
/* Body-sm, samma roll som fotnoten under en siffra får någon annanstans på
   sidan. Inte .note: raden bär ett påstående, inte en drifttid. */
.home .pspan__note{
  margin-top:16px;font-size:15px;line-height:1.6;color:var(--ink-muted);
  text-wrap:pretty;
}

/* Samma brytpunkt som .split, som är sidans etablerade ställe för två
   spalter till en. Under den blir hårstrecket vågrätt i stället för
   underförstått lodrätt, så halvorna fortsätter läsas som ett objekt och
   inte som två stycken efter varandra. */
@media (max-width:980px){
  .home .pspan{grid-template-columns:minmax(0,1fr);gap:28px}
  .home .pspan__act{padding-top:28px;border-top:1px solid var(--hair)}
}
/* ==== end: comment-round/pris ==== */

/* ==== added: comment-round/router ==== */
/* --- Segmentväxeln: fotots passform i luckan ------------------------------
   Fotot är 1024×768. Batteriskåpet står i vänstra halvan, x 55–570, med golvet
   bara ett par tiotal pixlar under sockeln och tom vägg till höger. Luckan är
   däremot 707×536 vid 1440 px och nästan kvadratisk strax över brytpunkten
   900, så object-fit:cover tog bort lika mycket från båda sidor och åt sig in
   i skåpets vänsterkant redan vid 1280.

   Beskärningen ska tas ur den tomma väggen, inte ur motivet: x ligger nära
   vänsterkanten så att skåpet står kvar helt i bild vid varje bredd, och y
   ligger lågt så att sockeln och golvlinjen är det sista som försvinner om
   bandet någon gång blir lägre än bilden är hög. */
.router.section--dark .router__media img{object-position:8% 82%}

@media (max-width:900px){
  /* I full bredd kan luckan få bildens egen proportion. 16:10 tvingade fram en
     vertikal beskärning på ~130 bildpixlar, mer än de ~110 som finns att ta av
     över och under skåpet, så sockeln kapades. I 4:3 beskärs ingenting alls. */
  .router.section--dark .router__media{aspect-ratio:4/3}
}
/* ==== end: comment-round/router ==== */

/* ==== added: from-variation-1 ==== */
/* ==========================================================================
   Adopted from design-variation-1, 2026-07-31, on Julius's direction: the
   type ramp and the spacing that goes with it, and nothing else.

   Deliberately NOT taken from that branch: the 32/24/14px radii and the pill
   controls, the blocks-and-air layout that lifts every band off the page
   ground, the floating dark header, the hairline removal, the footer, the
   link-arrow treatment and the hero scrim rewrite. Those are a different
   design; this file keeps its own.

   The token changes sit up in :root. What follows is only the weight,
   leading and tracking that the shorter ramp needs to hold together, plus
   the display figures, which were sized by eye against a 67px headline and
   have to step down with it or they become the page.
   ========================================================================== */

/* --- Type ----------------------------------------------------------------
   Weight 500 across the ramp, leading opened, and the display tracking
   backed almost all the way out. -.035em was tuned for 67px; at 32 it closes
   the counters and the heading reads as one word. */
.h1{font-weight:500;line-height:1.14;letter-spacing:-.018em;text-wrap:balance}
.h1--hero{font-weight:500;line-height:1.12;letter-spacing:-.02em}
.h2{font-weight:500;line-height:1.22;letter-spacing:-.012em;text-wrap:balance}
.h2--sm{font-weight:500;line-height:1.28;letter-spacing:-.01em}
.h3,.h4{font-weight:500;line-height:1.35;letter-spacing:-.008em}
.h3,.h4,.accordion__title,.tab,.person__name,.contact-card a,.contact-card p,
.intent__label,.track__title,.analys__title,.hcard__name,
.artcard__title,.bento__item--feature .artcard__title{letter-spacing:-.008em}
.lead{line-height:1.55;letter-spacing:0}
.eyebrow{font-size:14px;font-weight:500;letter-spacing:0}
.tab,.accordion__title{font-weight:500}
body{line-height:1.65}

/* --- The display figures step down with the ramp -------------------------
   Nine figures were measured against a 67px h1 and a 48px h2. Against a 48px
   h1 a 112px number stops being a big number in a page and becomes the page.
   Each keeps its rank in the ladder; the whole ladder just moves. This is
   what makes the avkastning block ("Först sjunker elräkningen") read the way
   Julius liked it in the variation. */
.bignum__fig{font-size:clamp(44px,calc(30.18px + 3.544vw),72px)}
.savings__figure{font-size:clamp(40px,calc(30.13px + 2.532vw),60px)}
.total__fig{font-size:clamp(34px,calc(27.09px + 1.772vw),48px)}
.compare__col--good .compare__figure{font-size:clamp(32px,calc(26.08px + 1.519vw),44px)}
.rehero__body .h1{font-size:var(--fs-h1-hero)}
/* The 60px cap existed only because --fs-h1 topped out at 67 and broke this
   heading over three lines in a 538px column. At 48 it sets as two. */
.page-steg .page-head h1{font-size:var(--fs-h1)}

/* --- Hero, spacing only --------------------------------------------------
   The proof rail pushes to the top of the picture and the brand rail to the
   bottom, so the headline floats in the middle of the photograph instead of
   sitting on top of a stack. The scrim is NOT changed: that was a colour
   decision in the variation and this file keeps its own. */
/* KORTARE ÄN FÖNSTRET, med flit. Var min(100svh,1040px), alltså exakt hela
   skärmen. Eftersom allt överskott ligger i ett enda glapp — det över
   märkesraden — blev det glappet 197 px vid 900 och 329 px vid 1080, och då
   låg logotyperna för långt från knapparna för att läsas som samma block.
   90svh med tak på 850 drar ihop det till 107 respektive 147 utan att röra
   någon annan siffra: glappet ÄR heron minus det fasta innehållet, så en
   kortare hero är samma sak som ett kortare glapp.
   Att sluta strax före fönstrets underkant är dessutom vad man vill: 90 px av
   nästa avsnitt syns, och då vet man att det finns mer att rulla till. */
.hero{min-height:min(90svh,850px);height:auto;max-height:none}
/* Sidhuvudet ligger ovanpå fotot och är ungefär 115 px högt, så av de gamla
   142 px återstod 27 mellan menyn och bevisraden: raden såg fastklämd ut mot
   sidhuvudet. 176 ger den 60 px luft, samma andning som bandets nederkant. */
.hero__body{padding:176px var(--gutter) 48px}
/* HÖJDFRÅGA, inte breddfråga. Vid 1280x720 är summan av fasta mått 176 topp,
   rubrikblock, märkesrad och 48 botten större än vad som får plats med ett
   gruppglapp värt namnet. Toppvadderingen är det enda som kan ge efter, och
   140 lämnar fortfarande 26 px mellan sidhuvudets underkant och bevisraden.
   Bredden är oförändrad: en 1280 px bred skärm är inte trång på bredden. */
@media (min-width:901px) and (max-height:820px){
  .hero__body{padding-top:140px}
}

/* TOMRUMMEN ÄR INTE LÄNGRE LIKA STORA, och det är hela poängen.

   Här stod tidigare två auto-marginaler, en nedåt på bevisraden och en uppåt
   på märkesraden, som delade allt överskott lika. Symmetriskt och fel, för
   överskottet är inte en konstant: det är fönstrets höjd minus innehållet.
   Uppmätt blev gruppglappet

     1280x720   28,4 px      1440x900   118,3 px
     1366x768   52,3 px      1512x982   159,3 px
     390x844    15,9 px      1440x1080  188,3 px

   alltså en spännvidd på 11,8 gånger, och inget av talen ligger på sajtens
   fyrapixelskala eftersom de alla är rester.

   Värre: INNANFÖR rubrikblocket är glappen 20 px (rubrik till ingress) och
   32 px (ingress till knappar). Vid 1280x720 och på varje telefon blev
   gruppglappet MINDRE än det interna. Då säger närhetsprincipen fel sak:
   bevisraden läses som en del av rubriken i stället för som ett eget stycke.
   Regeln är internt <= externt, och den gällde inte på en 13-tums skärm.

   Nu: gruppglappet är ett fast mått, och ALLT överskott hamnar under
   knappraden i stället för att delas. Två saker faller ut av det på en gång.
   Glappet kan aldrig underskrida 40 px, alltså aldrig hamna under det interna
   32. Och textblocket flyttar upp, från 54-56 % ned till 40-47 % beroende på
   höjd, vilket är där ett block ska ligga: optiskt centrum sitter något ovanför
   det geometriska, annars ser kompositionen ut att sjunka.

   Märkesraden behåller sin auto-marginal och står alltså kvar mot underkanten.
   Den ska ligga där: den är en fot, inte ett steg i läsordningen.

   btn-row bär samma mått som golv. Auto-marginalen ovanför märkesraden lägger
   till överskottet, men när fönstret är för lågt för att det ska finnas något
   blir auto noll, och då är det golvet som håller linjen borta från knappen. */
.hero__body{--herolucka:clamp(40px,4.4vh,64px)}
.hero__trust{margin-bottom:var(--herolucka);padding-bottom:24px}
.hero__brands{margin-top:auto;padding-top:24px}
.hero .btn-row{margin-bottom:var(--herolucka)}
.hero h1{margin-bottom:20px}
.hero__lead{margin-bottom:32px}
/* SAMMA HÖGERKANT SOM RUBRIKEN. Rubriken är 20ch vid 54 px och slutar på 612;
   ingressen var 46ch vid 24 px och slutade på 673,6. Elva och en halv pixel
   ifrån är inte ett eget mått, det ser ut som ett misslyckat försök att linjera.
   45ch ger 612,0 vid de här graderna. Ändras någon av de två typstorlekarna
   måste talet mätas om — det är därför det står utskrivet här och inte som en
   hjälpklass i markupen. Radlängden blir 45 tecken, vilket ligger inom det
   vanliga spannet för brödtext. */
.hero__lead{max-width:45ch}
@media (max-width:900px){
  .hero{height:auto;min-height:88svh;max-height:none}
  /* 128 och inte 142: sidhuvudet är 108 px på telefon, så 20 px luft under
     menyn räcker, och de fjorton pixlarna behövs längre ned. På en telefon
     fyller innehållet nästan hela höjden, och då är det toppvadderingen som
     avgör om blocket hamnar mitt på bilden eller under den. */
  .hero__body{padding:128px var(--gutter) 40px}
  /* STRAMARE INUTI, så att gruppglappet syns. På bred skärm är glappet 40 mot
     32 internt, alltså 1,25 gånger, och det räcker när det finns luft omkring.
     På telefon fanns ingen luft: 36 mot 32 hade varit en skillnad man mäter
     sig till, inte en man ser. Rubrikblockets egna mått dras därför ihop till
     16 och 24, och då står gruppglappet 1,5 gånger över det interna. Blocket
     blir 12 px kortare på köpet, vilket är rena vinsten på en höjd som inte
     räcker till ändå. */
  .hero__body{--herolucka:36px}
  .hero h1{margin-bottom:16px}
  .hero__lead{margin-bottom:24px}
  .hero__trust{padding-bottom:20px}
  .hero__brands{padding-top:20px}
}
/* ==== end: from-variation-1 ==== */

/* ==== added: mobile-nav ==== */
/* --- The phone menu ------------------------------------------------------
   Three problems, all only visible below 1100px.

   1. Eleven links in one flat list, 916px tall on a 390 screen. The panel
      now carries the same six-plus-Mer split the desktop bar has, with the
      overflow in a <details> so the menu opens short.
   2. Over the hero the header is transparent, so opening the panel put a
      white sheet directly under a see-through bar with a photograph showing
      through it. While the panel is open the header takes its solid state.
   3. The two logo files are different lockups: 3.67:1 dark, 2.50:1 white.
      Sized by width they render at different heights, which is why the
      overlay logo was 84px against the opaque one's 132. Sizing by height
      makes them the same size and lets both grow.
   ------------------------------------------------------------------------ */
@media (max-width:1100px){

  /* --- 3. One height for both lockups --------------------------------- */
  .site-header__logo img,
  .site-header--overlay .site-header__logo img{width:auto;height:40px}

  /* --- 1. Mer, as a disclosure ---------------------------------------- */
  .nav-more-m{border-bottom:1px solid var(--hair)}
  .nav-more-m>summary{
    display:flex;align-items:center;gap:8px;
    padding:16px 0;cursor:pointer;list-style:none;
    font-family:var(--sans);font-size:16px;font-weight:500;color:var(--ink-muted);
  }
  .nav-more-m>summary::-webkit-details-marker{display:none}
  .nav-more-m>summary:hover{color:var(--ink)}
  .nav-more-m>summary .nav-more__caret{
    width:10px;height:6px;flex:none;transition:transform .2s var(--ease-out);
  }
  .nav-more-m[open]>summary{color:var(--ink)}
  .nav-more-m[open]>summary .nav-more__caret{transform:rotate(180deg)}
  /* The nested list is inset so a sub-item is never mistaken for a top-level
     one, and its last row drops its rule because the group already has one. */
  .nav-more-m>ul{padding-left:14px;border-left:1px solid var(--hair);margin-bottom:4px}
  .nav-more-m>ul>li:last-child{border-bottom:0}
  .nav-more-m>ul>li>a{font-size:15px}

  /* --- 2. Open panel forces the header solid -------------------------- */
  /* :has() rather than a class, so the state stays where it already lives:
     the panel's own data-open, which js/site.js sets. Nothing new to keep in
     sync, and it still works if the script never runs. */
  /* POSITIONEN RÖRS INTE. Här stod position:relative, och det var felet:
     .site-header--overlay är position:fixed, så en växling till relative
     lyfter huvudet ur det fixerade lagret och lägger tillbaka det på sin
     plats i flödet — alltså överst på sidan. Öppnade man menyn 1800 px ned
     hamnade den 1732 px OVANFÖR vyn och försvann. Uppmätt just så.

     Bara bakgrunden ska ändras: huvudet ligger över ett foto på startsidan
     och måste bli ogenomskinligt när panelen fälls ut. Det var vad regeln
     ville, och positionen följde med av misstag.

     Höjden hålls inom vyn i stället. Huvudet blir en kolumn med tak på
     100dvh — dvh och inte vh, för adressfältet på en telefon äter av vh och
     panelen hade stuckit ut under skärmkanten. Panelen är den som får krympa
     och scrolla; min-height:0 krävs för att en flexbarn ska få bli mindre än
     sitt innehåll, och overscroll-behavior hindrar att sidan bakom börjar
     rulla när man nått listans slut. */
  /* GÄLLER BÅDA HUVUDENA. Reglerna nedan satt på .site-header--overlay, alltså
     bara på sidor med foto bakom huvudet. På alla andra sidor — produkter,
     galleri, kontakt — föll menyn tillbaka på sitt gamla beteende: den fällde
     ut huvudet i flödet till 802 px på en 844 px skärm och lämnade 43 pixlar
     där sidan bakom stack fram under knappen. Samma sliskiga kant som på
     startsidan, på tio sidor till, och den enda anledningen till att den inte
     rättades samtidigt var att väljaren råkade ha --overlay i sig.
     Det som ÄR overlay-specifikt — logotypbytet, knappens kant och den mörka
     utility-raden — står kvar scopat längre ned. */
  .site-header:has(#nav-panel[data-open="true"]){
    background-color:var(--surface-raised);
    display:flex;flex-direction:column;
    /* height och inte max-height. Med max-height blev huvudet bara så högt som
       innehållet krävde — 795 px på en 844 px skärm — och de sista 49 pixlarna
       visade sidan bakom. Menyn såg ut att sväva ovanpå ett dokument i stället
       för att vara en meny, och det som stack fram var en avhuggen rubrik.
       Full vyhöjd, i dvh så adressfältet inte äter av den. */
    height:100dvh;
  }
  /* KRYMPER INTE LÄNGRE. .site-header__inner har margin:0 auto för att
     centreras under sin max-width, vilket är rätt så länge föräldern är ett
     block. När huvudet blir en flex-kolumn betyder samma auto-marginal något
     annat: den suger upp allt ledigt utrymme i tväraxeln, så raden slutade
     sträcka sig och krympte till sitt innehåll — 288 px mitt på en 390 px
     skärm, med 51 px tomt på var sida.
     Det var det Julius såg som att "loggan och menyikonen sitter ihop": de satt
     inte ihop av sig själva, de var inklämda i en behållare som var 100 px
     smalare än skärmen. Linjen under huvudet avslöjade det — den gick inte
     ut till kanterna.
     align-self:stretch upphäver auto-marginalens verkan i tväraxeln utan att
     röra regeln som gäller på desktop. */
  .site-header:has(#nav-panel[data-open="true"]) .site-header__inner,
  .site-header:has(#nav-panel[data-open="true"]) .utility{
    align-self:stretch;width:100%;
  }
  /* Panelen tar resten av höjden och rullar i sig själv. flex:1 krävs nu när
     huvudet har en fast höjd, annars lägger sig panelen på sin innehållshöjd
     och lämnar en tom yta under sig. min-height:0 krävs för att ett flexbarn
     ska få bli lägre än sitt innehåll, och overscroll-behavior hindrar att
     sidan bakom börjar rulla när listan tar slut. */
  .site-header:has(#nav-panel[data-open="true"]) #nav-panel{
    flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  }
  .site-header--overlay:has(#nav-panel[data-open="true"]) .site-header__inner{
    border-bottom:1px solid var(--hair);
  }
  .site-header--overlay:has(#nav-panel[data-open="true"]) .site-header__logo--dark{display:block}
  .site-header--overlay:has(#nav-panel[data-open="true"]) .site-header__logo--light{display:none}
  .site-header--overlay:has(#nav-panel[data-open="true"]) .nav-toggle{
    border-color:var(--hair);color:var(--ink);
  }
  /* The utility strip is styled for a photograph behind it, so on a white
     header its text went white on white. With the panel open the whole
     header simply takes its ordinary opaque state, which is what every
     non-hero page already shows. */
  .site-header--overlay:has(#nav-panel[data-open="true"]) .utility{
    background-color:var(--dark);color:var(--dark-body);border-bottom:0;
  }
}
/* ==== end: mobile-nav ==== */

/* ==== added: comment-round-2 ==== */
/* ==========================================================================
   Startsidan, andra kommentarsvändan (index.html).
   Owner key: comment-round-2. Allt här är scopat till .home eller till en
   klass som bara startsidan använder.
   ========================================================================== */

/* .eyebrow--lead är borttagen. Den satte etiketten "Vad du får" i den ruta
   som flyttades till boka.html; ingen sida använder klassen längre. */

/* --- Reason-chippen ett snäpp större -------------------------------------
   Chippen satt på --fs-mono-sm, alltså 12→13px, vilket är fotnotsgrad. De
   är sektionens enda vägvisare mellan fyra kort och tappade mot brödtexten
   under sig. 14→15px är omkring 15 % större, plus lite mer luft i pillret så
   proportionen mellan text och kant står kvar. */
.home .reason__tag{
  padding:6px 14px;
  font-size:clamp(14px,calc(13.51px + 0.127vw),15px);
}

/* --- Avkastningsblocket: grunden överst -----------------------------------
   Vilket hus och vilken anläggning talen gäller stod förut i en fotnot under
   staplarna, alltså efter att läsaren redan tolkat dem som ett allmänt
   löfte. Raden ligger nu först. Formen är fyra nyckeltal på rad med
   hårstreck emellan: kompakt nog att läsas som en enda mening, tydlig nog
   att gå att kontrollera post för post. */
.home .swing__basis{
  display:flex;flex-wrap:wrap;gap:6px 0;
  margin:0 0 4px;padding:0;list-style:none;
  font-family:var(--sans);font-size:14px;line-height:1.4;color:var(--ink-muted);
}
.home .swing__basis li{padding:0 16px;border-left:1px solid var(--hair)}
.home .swing__basis li:first-child{padding-left:0;border-left:0}
.home .swing__basis b{font-weight:600;color:var(--ink);font-variant-numeric:tabular-nums}
/* Radbryts listan hänger avdelaren kvar på posten som hamnar först på nästa
   rad: den får 16 px indrag och ett lodrätt streck ut i ingenting, alltså två
   rader som inte står i linje med varandra. Under 720 px blir det i stället
   ett rutnät i två kolumner utan streck, där varje post börjar på sin egen
   vänsterkant. */
@media (max-width:720px){
  .home .swing__basis{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px 20px}
  .home .swing__basis li{padding:0;border-left:0}
}

/* .home .swing__group är borttagen. Den var mellanrubriken "Det här tar ner
   den" över tre fristående avdragsstaplar. De tre är nu segment i EN stapel
   under kostnadsstapeln, och då säger formen själv vad rubriken sa. */

/* --- Två staplar som sitter ihop ------------------------------------------
   .meter--parts tar godtyckligt många segment med bredden satt per segment i
   markupen, i stället för --a/--b på föräldern som .meter--split gör. Skälet
   är att den undre stapeln har tre poster och den övre en, och att bredderna
   är räknade ur beloppen: de hör ihop med sitt tal och ska stå bredvid det,
   inte i en variabel två nivåer upp.

   Runda hörn bara ytterst. Basregeln .meter span ger alla segment ett helt
   pillerformat, vilket på en delad bana får mittsegmentet att se ut som en
   egen fristående knapp mellan två avhuggna grannar. Ytterkanterna rundas,
   allt däremellan är rakt, och 3 px mellanrum räcker för att delningen ska
   synas. */
.home .meter--parts{display:flex;gap:3px;height:14px;overflow:visible;background:none}
.home .meter--parts>span{width:var(--w);background-color:var(--c);border-radius:0}
.home .meter--parts>span:first-child{border-radius:999px 0 0 999px}
.home .meter--parts>span:last-child{border-radius:0 999px 999px 0}
/* Ett ensamt segment är både :first-child och :last-child. De två reglerna
   ovan har samma specificitet, så den senare vinner rakt av och banan blev
   rundad till höger men avhuggen till vänster. Måste stå efter dem båda. */
.home .meter--parts>span:only-child{border-radius:999px}

/* Banorna ligger 4 px isär, vilket är litet nog att läsas som över- och
   underkant på samma sträcka. Där syns det direkt att de är lika långa, och
   det är hela argumentet: den undre täcker den övre exakt. */
.home .swing__pair{display:flex;flex-direction:column;gap:4px}
.home .swing__pair .swing__row--flush{gap:8px 16px}
/* Den undre raden har varken etikett eller siffra, så stapeln tar hela bredden. */
.home .swing__row--flush .swing__bar{grid-column:1/-1}

/* Tre nycklar under den undre banan, var och en lika bred som sitt segment.

   Beloppen ligger i nederkant, inte i överkant. Spalten är 607 px bred, så
   mittsegmentets fjärdedel är 126 px när paddingen är borträknad, och
   "Optimerad elförsäljning" ryms inte på en rad där. Radas nycklarna uppifrån
   hamnar 7 000 kr en rad lägre än 5 000 och 16 000, vilket ser ut som ett fel.
   Nedifrån står de tre talen på samma linje och etiketterna bryter uppåt.
   Kopplingen till segmentet bärs av färgstrecket, som går hela nyckelns höjd,
   inte av att texten råkar börja högst upp. */
.home .swing__keys--three{display:flex;gap:3px;align-items:stretch}
.home .swing__keys--three .swing__key{
  width:var(--w);border-left-color:var(--c);
  justify-content:flex-end;padding-right:8px;
}

@media (max-width:640px){
  /* "Optimerad elförsäljning" i en fjärdedel av 350 px är 85 px, alltså
     oläsbart. Nycklarna går ner på egna rader och färgstrecket i vänsterkanten
     är kvar, så kopplingen till segmentet överlever. Bredden är satt via --w
     och inte inline, just för att den här regeln ska kunna vinna. */
  .home .swing__keys--three{flex-direction:column;gap:8px}
  /* justify-content:flex-end är till för spaltläget ovan, där det lägger de
     tre beloppen på samma linje. Här är nyckeln en rad över hela bredden och
     samma regel skulle skjuta etikett och belopp till högerkanten, med
     färgstrecket kvar ensamt till vänster. */
  .home .swing__keys--three .swing__key{
    width:auto;padding-right:0;justify-content:flex-start;
  }
}

/* Slutsatsen är nu 0 kr och inte en summa som växer, så raden ska stå som
   ett svar och inte som ett facit i en tabell. Lite mer luft över strecket
   och en kraftigare etikett. */
.home .swing__sum{padding-top:28px}
.home .swing__sum-lbl{font-size:16px;font-weight:600}

/* --- #pris: tre nivåer i stället för en display-siffra --------------------
   Sektionen bar ett spann på en halv miljon som display-tal. Det är för
   brett för att någon ska kunna placera sig i det, så nivåerna står nu
   utskrivna och spannet har flyttat ned till .pspan__note under dem.
   .home .pspan__fig är därmed oanvänd på sajten och borttagen i samma vända.

   Raderna är etikett till vänster, pris till höger, med en punktlinje
   emellan: samma greppa-med-ögat som en innehållsförteckning, och det som
   gör att tre olika långa etiketter ändå läses som en kolumn. Priset i
   --brand och tabular-nums, alltså samma roll som varje annan siffra i
   blocket ovanför. */
.home .ptiers{margin:0;padding:0;list-style:none;border-top:1px solid var(--hair)}
.home .ptier{
  display:flex;align-items:baseline;gap:12px;
  padding:14px 0;border-bottom:1px solid var(--hair);
}
.home .ptier__what{font-size:16px;color:var(--ink);text-wrap:pretty}
/* Punktlinjen är en repeterad radial-gradient och inte en border-bottom, för
   att den ska sluta exakt där priset börjar oavsett hur långa de två är. */
.home .ptier::after{
  content:"";flex:1 1 auto;min-width:16px;align-self:flex-end;
  height:5px;margin-bottom:4px;
  background-image:radial-gradient(circle,var(--hair-strong) 1px,transparent 1px);
  background-size:5px 5px;background-position:left bottom;background-repeat:repeat-x;
}
.home .ptier__price{
  order:3;flex:none;
  font-family:var(--sans);font-size:17px;font-weight:600;letter-spacing:-.01em;
  color:var(--brand);font-variant-numeric:tabular-nums;white-space:nowrap;
}
@media (max-width:420px){
  /* 17px fetstil plus en etikett på tre ord får inte plats på en rad här.
     Priset går ned under sitt eget namn och punktlinjen tas bort, eftersom
     den inte längre binder ihop två ändar på samma rad. */
  .home .ptier{flex-wrap:wrap;gap:2px}
  .home .ptier::after{display:none}
  .home .ptier__price{order:2;width:100%}
}

/* --- Processraden: räls, stegetikett, bild, text -------------------------
   Julius: "the line with four circles and numbers doesn't really work". Han
   skickade en referens och beskrev ordningen: rälsen kvar, pluppar som är små
   och tomma, "Steg 1" utskrivet UNDER pluppen, sedan bilden, sedan texten.

   Siffran satt tidigare INNE i pluppen, som därför var 26 px och centrerad
   över spalten. Två saker gick fel med det. En nod med en siffra i ser ut att
   vara klickbar, och den var det inte. Och en centrerad nod linjerar inte med
   någonting: spaltens text börjar i vänsterkant, så pluppen svävade 128 px in
   över bilden utan att peka på något.

   Nu gör de två sakerna var sitt jobb. Rälsen med sina tomma pluppar säger att
   det är en ordnad följd och var varje spalt börjar; etiketten säger vilket
   steg det är. Pluppen sitter i spaltens vänsterkant, alltså exakt över
   etiketten, bilden och rubriken under den. Allt i spalten hänger på samma
   lodräta linje.

   Måtten är tagna ur referensen och sedan skalade: dess spalt är 560 px bred
   och våra är 257, så luften är minskad ungefär i proportion men inte under
   det som håller grupperna isär. */
.home .track{--rail-y:6px}
.home .track__step{padding-top:26px}

/* Pluppen: liten och tom. 9 px mot referensens ~10 vid dess skala. */
.home .track__step::before{
  content:"";
  left:0;
  width:9px;height:9px;margin-top:-4.5px;
  border:0;
  background-color:var(--brand);
}
.home .track__step:hover::before{transform:none}

/* Stegetiketten. Först satt den i en bård, efter referensen. Julius ville inte
   ha rutan, och han har rätt: bården var lånad från en komponent där chippet
   bar två fält med en avdelare emellan och därför behövde en ram som höll ihop
   dem. Här står ett ord och en siffra. En ram runt det är en låda utan
   innehåll att hålla ihop.
   Kvar är rollen: en etikett över en figur, alltså samma behandling som
   .eyebrow har på resten av sajten, i märkesfärg så att följden syns. */
.track__nr{
  display:block;align-self:flex-start;
  margin-bottom:18px;
  font-family:var(--sans);font-size:15px;font-weight:500;
  letter-spacing:0;color:var(--brand);
  white-space:nowrap;
}

/* Rälsen går mellan pluppmitterna som förut. Sista steget fick ingen alls, så
   linjen dog vid tredje pluppen och den fjärde stod lös; den har en egen som
   slutar i spaltens högerkant i stället för att sträcka sig över mellanrummet
   efter, eftersom det inte finns något mellanrum kvar där. */
@media (min-width:901px){
  .home .track__line{left:0;width:calc(100% + var(--gap))}
  .home .track__line--sist{width:100%}
}

/* Lodrätt läge: rälsen blir en stapel till vänster, pluppen sitter på den och
   etiketten står först i spalten. Nedre gränsen är avsiktlig, se noten om
   källordning i den gamla versionen av det här blocket: under 640 är rälsen
   dold längre upp i filen och ett indrag utan räls pekar på ingenting. */
@media (min-width:641px) and (max-width:900px){
  .home .track{--rail-y:9px}
  .home .track__step{padding-left:34px;padding-top:0}
  .home .track__line{left:4px;width:2px}
  .home .track__nr{margin-bottom:18px}
}
@media (max-width:640px){
  /* Under 640 är rälsen dold längre upp i filen. Pluppen måste följa med:
     en prick utan linje att sitta på är precis den lösa dekoration som hela
     omarbetningen skulle bort med. Stegetiketten bär ordningen ensam här,
     vilket är gott nog när spalterna ligger under varandra. */
  .home .track__step::before{display:none}
  .home .track__nr{margin-bottom:16px}
}

/* Rytmen i spalten. Referensens spalt är 560 px bred och våra är 257, så
   luften är skalad ned men proportionerna behållna: tätt mellan rubrik och
   brödtext, som hör ihop, och tydligt mer före raden längst ned, som är ett
   eget påstående.

   Taglinjen står kvar ordagrant, på Julius besked att ingen text får falla
   bort. Den var förut en fri rad som slutade på olika höjd i varje spalt
   beroende på hur lång brödtexten var. Nu ligger den mot en hårlinje och
   trycks ned av margin-top:auto, så alla fyra möts på samma höjd oavsett
   textlängd, och hårlinjerna bildar en egen vågrät linje längst ned i raden.
   Det är samma grepp som referensen använder för sin understa rad. */
.home .track__step .shot{margin-bottom:22px}
.home .track__title{margin-bottom:10px}
.home .track__body{margin-bottom:26px}
.home .track__tag{
  width:100%;margin-top:auto;padding-top:16px;
  border-top:1px solid var(--hair);
  align-items:flex-start;
}
.home 

/* ==== end: comment-round-2 ==== */

/* ==== added: hero-brand-carousel ==== */
/* --- Varumärkesraden som karusell ----------------------------------------
   Owner key: hero-brand-carousel. Bara startsidan har .hero__brands.

   Femton märken rymdes inte på en rad, så raden wrappade till två vid 1440 och
   tre på smalare skärmar. Ett block som växer nedåt när listan växer är fel
   form för en fotnot under hero. En karusell har i stället konstant höjd
   oavsett hur många leverantörer som läggs till.

   Loopen: .bmarquee__row innehåller två identiska uppsättningar och rullar
   -50 % av sin egen bredd, alltså exakt en uppsättning. Vid varvets slut står
   kopian precis där originalet stod, och hoppet tillbaka syns inte. Bredden
   får aldrig sättas i procent eller px någonstans i kedjan; width:max-content
   är det som gör att -50 % betyder "en uppsättning" och inte "halva
   viewporten".

   Mellanrummet mellan märken ligger som padding-right på varje uppsättning och
   inte som gap på raden. Annars saknas ett mellanrum i skarven mellan sista
   märket i den ena uppsättningen och första i den andra, och just den luckan
   är den enda platsen där ögat kan se var varvet börjar om. */
.hero__brands .bmarquee{
  flex:1 1 auto;min-width:0;overflow:hidden;
  /* Märkena tonar in och ut i kanterna i stället för att klippas mitt i ett
     ord. Måttet är samma som avståndet mellan två märken, så tonningen känns
     som en del av rytmen och inte som en ridå. */
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 28px,#000 calc(100% - 28px),transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0,#000 28px,#000 calc(100% - 28px),transparent 100%);
}
.hero__brands .bmarquee__row{
  display:flex;width:max-content;
  animation:bmarquee 52s linear infinite;
}
/* Specificiteten måste slå .hero__brands ul, som sätter flex-wrap, justify-
   content:space-between och gap. Ett wrappande spår i en karusell staplar
   märkena i höjdled i stället för att rulla dem. */
.hero__brands .bmarquee__set{
  display:flex;flex-wrap:nowrap;align-items:center;justify-content:flex-start;
  flex:none;gap:0 28px;padding-right:28px;margin:0;list-style:none;
  /* width:auto måste stå här. Telefonregeln längre upp sätter
     .hero__brands ul{width:100%}, och en procentbredd på ett flex-barn inne i
     en förälder med width:max-content är cirkulär: webbläsaren löser den, men
     uppsättningen mätte 1941 px under 1024 mot 1200 px över, alltså bredare på
     den skärm där typsnittet är mindre. Med bredden felmätt blir också -50 %
     fel och loopen hoppar. */
  width:auto;
}
.hero__brands .bmarquee__set>li{flex:none}
@keyframes bmarquee{to{transform:translateX(-50%)}}

/* Stanna när någon vill läsa ett namn. Pekdon-frågan är med för att
   :hover annars fastnar tänd efter en tryckning på en telefon. */
@media (hover:hover){
  .hero__brands .bmarquee:hover .bmarquee__row{animation-play-state:paused}
}

/* Utan animering: första uppsättningen står still och den andra ligger
   utanför overflow:hidden. Raden är då exakt det den var innan, en stillastå-
   ende rad märken, vilket är ett färdigt läge och inte ett halvt. */
@media (prefers-reduced-motion:reduce){
  .hero__brands .bmarquee__row{animation:none}
}

@media (max-width:900px){
  .hero__brands .bmarquee__set{gap:0 22px;padding-right:22px}
  .hero__brands .bmarquee{
    -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 18px,#000 calc(100% - 18px),transparent 100%);
    mask-image:linear-gradient(90deg,transparent 0,#000 18px,#000 calc(100% - 18px),transparent 100%);
  }
  /* Kortare varv på en smal skärm: samma hastighet i pixlar per sekund känns
     långsammare ju mindre av raden man ser åt gången. */
  .hero__brands .bmarquee__row{animation-duration:40s}
}
/* ==== end: hero-brand-carousel ==== */

/* ==== added: om-oss/teampair ==== */
/* --- Två gruppbilder i par, om-oss.html ----------------------------------
   Owner key: om-oss/teampair.

   Avsiktligt små. Personkorten ovanför är sidans innehåll; det här paret är
   bara belägg för att teamet finns och är fler än de sex som är namngivna.
   Därför en max-bredd på 640 px i en spalt som är 1180: bilderna ska läsas
   som en fotnot till korten, inte som ett eget block.

   Ramen kommer från .shot, som redan bär ram, radie, skugga och
   overflow:hidden. Här sätts bara rutnätet, proportionen och beskärningen. */
/* Ett foto, inte två. Den andra rutan var en märkt tom ram som väntade på en
   bild som inte kommer, och den togs bort 1 augusti 2026. Gruppbilden får hela
   bredden i stället för halva, vilket är dubbelt så stort som i paret. */
.teampair{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;
  max-width:none;margin-top:clamp(28px,3vw,40px);
}
/* 16:9 och inte 3:2. Gruppbilden är 1183x616, alltså 1,92:1. I en 3:2-ram
   (1,5:1) skär cover bort elva procent i vardera kanten, och på just den här
   bilden står en person i var ände: båda blev avhuggna. 16:9 är 1,78:1, alltså
   knappt fyra procent per sida, och då står ytterpersonerna kvar hela.
   Byts bilden ut måste det här räknas om. En gruppbild som kapar någon i
   kanten är sämre än ingen gruppbild. */
.teampair__item{aspect-ratio:16/9}
/* .shot centrerar sitt innehåll med place-items, vilket är rätt för en
   produktbild som ska sitta mitt i sin ruta men fel för en gruppbild som ska
   fylla ramen. Bilden får därför täcka hela ytan. */
.teampair__item img{width:100%;height:100%;object-fit:cover;display:block}

/* Tom ram i väntan på den andra bilden. Togs bort i markörrensningen och
   ÅTERSTÄLLDES samma dag: lagfotot är en av de platser Julius fortfarande ska
   fylla. Samma streckade gula markering som .bmark-ph i hero-raden, så en
   oifylld plats ser likadan ut överallt på sajten. */
.teampair__slot{
  display:grid;place-items:center;width:100%;height:100%;
  min-height:180px;
  background-color:var(--surface-raised);
  border:1px dashed rgba(255,192,0,.75);border-radius:calc(var(--radius) - 1px);
}
.teampair__slot span{
  font-family:var(--sans);font-size:var(--fs-mono-sm);color:var(--ink-faint);
}

@media (max-width:700px){
  .teampair{max-width:none}
}
/* ==== end: om-oss/teampair ==== */

/* ==== added: lonsamhet/refhouse ==== */
/* --- Referenshuset: förklaringen till startsidans stapeldiagram -----------
   Owner key: lonsamhet/refhouse.

   Blocket kom hit från startsidan. Startsidan visar staplarna och skickar
   vidare; vad varje post är hör hemma på den sida som redan lovar att visa
   varje antagande. Formen följer .assump rakt av, samma hårstreck och samma
   luft, så de två blocken läses som två delar av samma redovisning och inte
   som ett block plus ett tillägg. */
.refhouse{
  margin-top:clamp(32px,3.4vw,48px);
  padding-top:clamp(28px,3vw,40px);
  border-top:1px solid var(--hair-strong);
}
/* Definitionslista och inte rubrik plus stycke: varje post ÄR en term och
   dess förklaring, och en <dl> säger det åt en skärmläsare utan att fyra
   h4:or läggs till i sidans disposition. */
.refhouse__list{margin:clamp(24px,2.6vw,32px) 0 0}
.refhouse__list>div{
  display:grid;grid-template-columns:minmax(0,17rem) minmax(0,1fr);
  gap:6px 48px;padding:18px 0;border-top:1px solid var(--hair);
}
.refhouse__list>div:last-child{border-bottom:1px solid var(--hair)}
.refhouse__list dt{font-size:16px;font-weight:600;color:var(--ink);text-wrap:pretty}
.refhouse__list dd{
  margin:0;max-width:64ch;
  font-size:15px;line-height:1.6;color:var(--ink-muted);text-wrap:pretty;
}
@media (max-width:820px){
  /* 17rem för termen plus 48px ränna lämnar under 820 px för lite åt
     förklaringen, som är den längre av de två. Termen får en egen rad. */
  .refhouse__list>div{grid-template-columns:minmax(0,1fr);gap:6px}
}
/* ==== end: lonsamhet/refhouse ==== */

/* ==== added: index/whybento ==== */
/* --- Fyra skäl som ett bento-rutnät --------------------------------------
   Owner key: index/whybento.

   Första försöket var fyra lika stora textkort på rad. Texten blev kort,
   vilket var meningen, men formen blev en lista att skumma förbi: fyra lika
   tunga kort utan bild ger ögat ingenstans att landa och sektionen läses som
   information i stället för som ett påstående.

   Rutnätet ger de fyra olika vikt. Samma princip som .bento på artiklar.html,
   men egna klasser: artcard är byggd för en artikel med metarad, byline och
   datum, och ingenting av det finns här.

   Ytorna, på 1024 och uppåt:
     rad 1-2, kol 1-2   Först räknar vi     stort kort med bild
     rad 1,   kol 3     Lagring och styrning  blå ruta utan bild
     rad 2,   kol 3     Elbil hemma          litet kort med bild
     rad 3,   kol 1-3   Rätt monterat        brett kort, bild vid sidan

   Två av fyra bär bild. Fyra bilder hade gjort rutnätet till ett fotoalbum
   och noll hade gjort det till en lista igen. Den blå rutan bryter av på det
   ställe där ögat annars hade fortsatt rakt förbi. */
.home .whybento{
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(16px,1.6vw,22px);
  margin-top:clamp(36px,4vw,56px);
}
.home .wcard--feature{grid-column:1/3;grid-row:1/3}
.home .wcard--wide{grid-column:1/-1}

/* Hela kortet är länken, så hela ytan är klickbar och inte bara pilraden.
   Samma hover som .artcard: kanten mörknar och kortet lyfts 2 px. */
.home .wcard{
  display:flex;flex-direction:column;overflow:hidden;
  background-color:var(--surface-raised);
  border:1px solid var(--hair);border-radius:var(--radius);
  text-decoration:none;color:inherit;
  transition:border-color .25s ease,box-shadow .25s ease,transform .25s var(--ease-out);
}
.home .wcard:hover{
  border-color:var(--ink-faint);transform:translateY(-2px);box-shadow:var(--shadow-md);
}
.home .wcard__body{display:flex;flex-direction:column;gap:10px;flex:1;padding:clamp(20px,2vw,26px)}
/* Bilden ärver .shot men ska sitta kant i kant i kortet, utan egen ram. */
.home .wcard__shot{
  border:0;border-radius:0;box-shadow:none;
  border-bottom:1px solid var(--hair);aspect-ratio:16/9;
}
.home .wcard__shot img{width:100%;height:100%;object-fit:cover}

.home .wcard__tag{
  font-family:var(--sans);font-size:var(--fs-mono-sm);font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink-faint);
}
.home .wcard__title{font-size:20px;font-weight:600;line-height:1.28;letter-spacing:-.012em;text-wrap:pretty}
.home .wcard__text{font-size:15px;line-height:1.6;color:var(--ink-muted);text-wrap:pretty}
/* margin-top:auto binder pilen till kortets underkant, så alla fyra pilar
   ligger på sin egen botten oavsett hur mycket text som står ovanför. */
.home .wcard__more{
  margin-top:auto;padding-top:4px;
  font-family:var(--sans);font-size:15px;font-weight:500;color:var(--brand);
}
.home .wcard:hover .wcard__more{text-decoration:underline;text-underline-offset:3px}

/* Det stora kortet får större rubrik och en bild som tar den plats som finns
   kvar när texten är satt, i stället för ett fast bildförhållande. */
.home .wcard--feature .wcard__title{font-size:clamp(22px,1.9vw,27px)}
.home .wcard--feature .wcard__shot{aspect-ratio:auto;flex:1 1 auto;min-height:230px}

/* Blå ruta, sidans etablerade mörka botten. Ingen bild: den här ytan är till
   för att bryta rytmen, inte för att visa ett batteri en gång till. */
.home .wcard--blue{
  background-color:var(--brand);border-color:var(--brand);color:var(--dark-fg);
}
.home .wcard--blue .wcard__tag{color:rgba(255,255,255,.62)}
.home .wcard--blue .wcard__text{color:rgba(255,255,255,.86)}
.home .wcard--blue .wcard__more{color:var(--accent)}
.home .wcard--blue:hover{border-color:var(--brand-300)}

/* Breda kortet ligger vågrätt: bild till vänster, text till höger. Det ger
   raden under rutnätet en egen rytm i stället för ett fjärde kort i samma
   form som de andra. */
.home .wcard--wide{flex-direction:row;align-items:stretch}
.home .wcard--wide .wcard__shot{
  flex:0 0 38%;aspect-ratio:auto;border-bottom:0;border-right:1px solid var(--hair);
}
.home .wcard--wide .wcard__body{justify-content:center;max-width:64ch}
/* margin-top:auto skjuter pilen till kortets underkant, vilket är rätt i de
   tre lodräta korten där pilarna ska ligga på samma linje. Här slåss den mot
   justify-content:center och lämnade pilen ensam långt under texten. I ett
   vågrätt kort finns ingen annan pil att ligga i linje med. */
.home .wcard--wide .wcard__more{margin-top:0}
/* Bilden är 16:9 och skulle annars styra kortets höjd genom sin egen
   proportion på 38 % av bredden. Höjden sätts av texten i stället, och
   bilden beskärs mot den. */
.home .wcard--wide{min-height:0}
.home .wcard--wide .wcard__shot img{height:100%}

@media (max-width:1023px){
  /* Två spalter. Det stora kortet behåller sin bredd men slutar spänna två
     rader, och det breda lägger sig över båda spalterna. */
  .home .whybento{grid-template-columns:repeat(2,minmax(0,1fr))}
  .home .wcard--feature{grid-column:1/-1;grid-row:auto}
  .home .wcard--feature .wcard__shot{aspect-ratio:16/9;min-height:0}
}
@media (max-width:720px){
  .home .whybento{grid-template-columns:minmax(0,1fr)}
  /* Vågrätt kort på en telefon ger 38 % bild och 62 % text, alltså ungefär
     130 px bildbredd och en textspalt på 20 tecken. Det staplas. */
  .home .wcard--wide{flex-direction:column}
  .home .wcard--wide .wcard__shot{
    flex:none;aspect-ratio:16/9;border-right:0;border-bottom:1px solid var(--hair);
  }
}
/* ==== end: index/whybento ==== */

/* --- Avkastningsblockets fot, ihoptryckt -----------------------------------
   Slutsatsen, noten och länken låg som tre block under varandra och tog
   196 px, alltså mer lodrätt utrymme än de fyra staplarna de sammanfattar.

   Första försöket la noten och länken sida vid sida på en rad. Det blev
   större, inte mindre: noten kapad till 48ch bröts på två rader och länken
   fick ingen plats bredvid, så raden staplade och foten växte till 211 px.

   Det som fungerar är att sluta möblera och slå ihop. Noten och länken är ett
   stycke, med länken som sista mening i det. Två rader i stället för tre
   block, och siffran ett steg mindre: den är blockets slutpunkt, inte dess
   största objekt. Staplarna är argumentet. */
.home .swing__sum{padding-top:18px}
.home .swing__sum-fig{font-size:clamp(26px,calc(22.3px + 0.95vw),32px)}
.home .swing__thesis{
  max-width:46ch;margin:14px 0 0;
  font-size:clamp(16px,calc(14.4px + 0.4vw),19px);line-height:1.45;
  font-weight:500;color:var(--ink);text-wrap:pretty;
}
.home .swing__foot{
  max-width:74ch;margin-top:10px;
  font-size:13px;line-height:1.55;color:var(--ink-faint);text-wrap:pretty;
}
/* Länken ärver inte .link-arrow: den ska ligga i textflödet som en mening,
   inte som ett eget objekt med egen radhöjd under stycket. */
.home .swing__foot a{
  color:var(--brand);font-weight:500;
  text-decoration:underline;text-underline-offset:3px;
  text-decoration-color:var(--brand-100);
}
.home .swing__foot a:hover{text-decoration-color:var(--brand)}

/* ==== added: index/prow ==== */
/* --- Pris som en rad, inte som en panel ----------------------------------
   Owner key: index/prow.

   Nivåerna låg i .pspan: en inramad panel delad i två halvor. Avkastnings-
   blocket rakt ovanför är också en tvådelad yta, och två likadana paneler
   efter varandra läses som en mall. Det här är dessutom sektionen med minst
   innehåll på sidan, tre tal och ett telefonnummer, och den bar mest möbler.

   Ingen ram, ingen botten, ingen andra spalt. Tre kolumner med ett hårstreck
   över var och en, alltså samma grepp som .why-korten fast utan kort. Priset
   är radens enda framträdande element; etiketten över det är en ledtext.

   .home .pspan, .ptiers och .ptier används inte längre på någon sida. De
   ligger kvar i filen tills någon städar, precis som .plist gjorde före dem. */
/* TVÅ SPALTER MED SIDORNA OMVÄNDA. Talen till vänster, rubriken till höger.

   Ordningen i markupen är rubrik först, tal sedan: det är läsordningen för en
   skärmläsare och den ordning sektionen staplar i på smal skärm. Bytet sker
   bara här, med grid-column, så ingenting i innehållet behöver stå i en
   ordning som bara är sann på bred skärm.

   Talen får den smalare spalten. Tre korta rader väger ändå tyngre än sin
   bredd, eftersom priset är satt i --brand och hårstrecken drar ögat.

   Talblocket är centrerat mot högerspalten, inte ställt i överkant. Ställt i
   överkant slutade det halvvägs ned och lämnade en tom nedre vänsterkvadrant
   lika hög som blocket självt, eftersom rubrikspalten är dubbelt så hög: den
   bär rubrik, ingress, mening och knapp. Centrerat delas luften lika över och
   under, och de tre talen möter rubriken på mitten. */
.home .pris-split{
  display:grid;grid-template-columns:minmax(0,.82fr) minmax(0,1fr);
  column-gap:clamp(32px,4vw,64px);row-gap:clamp(24px,2.6vw,32px);
  align-items:start;
}
.home .pris-split > .pris-head{grid-column:2;grid-row:1;margin-bottom:0}
.home .pris-split > .pris-figures{grid-column:1;grid-row:1/span 2;align-self:center}
/* Finstilt under priserna. Egen regel: raden hör till talen, inte till
   avslutsraden i högerspalten, och ska läsas som en fotnot till dem. */
.home .prow__note{
  margin:12px 0 0;
  font-size:13px;line-height:1.55;color:var(--ink-faint);text-wrap:pretty;
}
.home .pris-split > .prow__foot{grid-column:2;grid-row:2}

/* En spalt, inte tre. Hårstrecket blir avdelaren mellan raderna, samma grepp
   som den smala skärmen redan använde. */
.home .prow{
  display:grid;grid-template-columns:minmax(0,1fr);
  gap:0;margin:0;padding:0;list-style:none;
}
.home .prow__item{
  display:flex;flex-direction:column;gap:5px;
  padding:16px 0;border-top:1px solid var(--hair);
}
.home .prow__item:first-child{border-top-color:var(--hair-strong)}
.home .prow__item:last-child{border-bottom:1px solid var(--hair)}
.home .prow__what{font-size:15px;color:var(--ink-muted);text-wrap:pretty}
.home .prow__price{
  font-family:var(--sans);font-size:clamp(19px,1.5vw,23px);font-weight:600;
  line-height:1.2;letter-spacing:-.015em;
  color:var(--brand);font-variant-numeric:tabular-nums;text-wrap:pretty;
}
/* Avslutsraden: meningen till vänster, telefonen till höger, på samma
   baslinje. Ingen andra knapp och ingen öppettidsnot; båda finns redan i
   avslutsbandet längst ned på sidan. */
/* Avslutsraden ligger nu inuti rubrikspalten, så den staplar i stället för att
   spänna över sidan: meningen på en rad och knappen under den, vänsterställd
   med rubriken ovanför. */
.home .prow__foot{
  display:flex;flex-direction:column;align-items:flex-start;
  gap:20px;margin-top:0;
}
.home .prow__foot p{margin:0;color:var(--ink-muted)}
@media (max-width:900px){
  /* En spalt, i markupens egen ordning: rubrik, tal, handling. */
  .home .pris-split{grid-template-columns:minmax(0,1fr);gap:clamp(28px,4vw,40px)}
  .home .pris-split > .pris-head,
  .home .pris-split > .pris-figures,
  .home .pris-split > .prow__foot{grid-column:1;grid-row:auto;align-self:stretch}
}
@media (max-width:560px){
  .home .prow__foot .btn{width:100%;justify-content:center}
}
/* ==== end: index/prow ==== */

/* Leverantör utan logotypfil. Samma idé som .bmark-ph i hero-raden: namnet
   satt i sidans egen text med en streckad gul underkant, så att en oifylld
   plats ser likadan ut överallt på sajten och inte kan förväxlas med en
   avsiktlig textlogotyp. Ljus botten här, till skillnad från hero, så färgen
   är --ink-muted och inte vit. Räknas av tools/placeholders.py. */
.supplier__ph{
  display:inline-block;
  font-family:var(--sans);font-size:15px;font-weight:600;
  line-height:28px;letter-spacing:.01em;color:var(--ink-muted);
  border-bottom:1px dashed rgba(255,192,0,.9);
  white-space:nowrap;
}

/* Videokort som väntar på sin film. Nordmount har ingen monteringsvideo för
   korrugerad plåt eller papp på de sidor Julius länkade, så kortet visar
   texten men ingen spelknapp. Ramen står kvar med den streckade markeringen,
   samma språk som övriga oifyllda platser på sajten, i stället för att ett
   dött spelreglage lovar en video som inte finns. */
/* HÄR LÅG .video-card--pending, den streckade rutan med texten "Video kommer".
   Borttagen 17 augusti 2026: de två kort som bar den, korrugerad plåt och
   papptak, har fått sina filmer och ingen markup använder klassen längre.
   Behövs den igen står den i git-historiken. */

/* --- Halv stjärna för snittbetyget ---------------------------------------
   Snittet 4,6 ritades som fyra fyllda stjärnor och en tom, alltså exakt samma
   bild som ett betyg på 4,0. Det drar ned betyget ett helt steg i den enda
   delen av raden folk faktiskt tittar på, och det är siffran bredvid som är
   den sanna. Nu fylls den femte till hälften.

   Två stjärnor ovanpå varandra i stället för en gradient: <use> ärver fill
   från elementet, och en CSS-gradient går inte att sätta som fill utan att
   definiera den i SVG:n. Den undre är hårstrecksgrå, den övre klippt på
   mitten med clip-path. Samma sprite som alla andra stjärnor, ingen ny symbol.

   Elementet är aria-hidden: hela raden bär redan sitt betyg i .stars
   aria-label ("4,6 av 5 i snitt"), så en skärmläsare hör siffran och inte en
   uppräkning av stjärnor. */
.stars__half{
  position:relative;display:inline-block;flex:none;
  width:15px;height:15px;line-height:0;
}
.stars--sm .stars__half{width:14px;height:14px}
.stars__half svg{position:absolute;inset:0;width:100%;height:100%}
.stars__half svg:first-child{fill:var(--hair)}
.stars__half svg:last-child{fill:var(--accent);clip-path:inset(0 50% 0 0)}

/* Märkeskorten låg en sväng på liggande led, med bilden i en smal spalt till
   vänster och texten till höger, för att spara höjd. Återställt på Julius
   besked 1 augusti: han vill ha dem stående. Rutnätet är alltså .brands som
   förut, tre spalter med bilden överst.

   Det som föranledde försöket står kvar som ett öppet problem: sex vita
   produkter fotograferade mot vitt tar 910 px och skiljer sig knappt åt. Om
   höjden ska ned igen är vägen dit troligen färre kort eller mindre bildruta,
   inte liggande kort. */

/* --- Montageordningen som numrerad räls ----------------------------------
   Owner key: produkter/mseq.

   Ordningen stod som en mening i noten: "infästning, skena, panel, klämma".
   Fyra moment i löpande text läses förbi. Som numrerad räls blir de en bild av
   förloppet, och den som sedan öppnar en monteringsvideo vet vad hon tittar
   efter innan filmen börjar.

   Samma idé som processraden på startsidan, men utan bilder. Momenten är fyra
   och videorna under raden visar dem i rörelse; ett foto per steg hade sagt
   samma sak en tredje gång på samma skärm. Siffran ligger i ett cirkelmärke
   som .home .track__step, alltså samma språk för "steg N av fyra" på hela
   sajten. */
.mseq{
  counter-reset:mseq;
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(20px,2.2vw,30px);
  margin:clamp(28px,3vw,40px) 0 clamp(32px,3.4vw,44px);padding:0;list-style:none;
}
.mseq__step{position:relative;padding-top:38px}
.mseq__step::before{
  counter-increment:mseq;content:counter(mseq);
  position:absolute;top:0;left:0;
  display:grid;place-items:center;width:26px;height:26px;border-radius:50%;
  background-color:var(--brand);color:#fff;
  font-family:var(--sans);font-size:13px;font-weight:600;line-height:1;
}
/* Hårstrecket binder ihop de fyra till en sekvens i stället för fyra rutor.
   Sista steget får inget, av samma skäl som .track__step:last-child. */
.mseq__step::after{
  content:"";position:absolute;top:13px;left:34px;right:calc(-1 * clamp(20px,2.2vw,30px));
  height:1px;background-color:var(--hair-strong);
}
.mseq__step:last-child::after{display:none}
.mseq__name{margin-bottom:6px;font-size:17px;font-weight:600;letter-spacing:-.008em}
.mseq__body{font-size:14.5px;line-height:1.55;color:var(--ink-muted);text-wrap:pretty}

@media (max-width:860px){
  .mseq{grid-template-columns:repeat(2,minmax(0,1fr));gap:24px}
  /* Strecket skulle peka ut i tomma luften i slutet av varje rad. */
  .mseq__step:nth-child(2n)::after{display:none}
}
@media (max-width:520px){
  .mseq{grid-template-columns:minmax(0,1fr);gap:20px}
  .mseq__step{padding-top:0;padding-left:38px}
  .mseq__step::after{display:none}
}

/* --- Valet mellan systemformerna, med produkten i kortet ------------------
   Owner key: produkter/optcard.

   De två korten bar sidans centrala val, all-in-one mot fristående, som två
   grå textblock utan en enda bild. Ett val mellan två systemformer är svårt
   att föreställa sig; två produkter bredvid varandra är det inte.

   Bilderna är inte nya: det är samma Sigenergy och samma Solis som står i
   märkesraden längre ned, alltså produkter vi faktiskt monterar och som ÄR
   var sitt alternativ. Läsaren möter dem två gånger på sidan och känner igen
   dem andra gången. Ramen är .media-slot, samma som märkeskorten, så
   trim-uträkningen för bildhöjden gäller oförändrad.

   Rutan är låg med flit. Den ska säga "det ser ut så här", inte ta över
   kortet från texten som förklarar skillnaden. */
.optcard{padding:0;overflow:hidden}
.optcard__shot{
  --slot-h:clamp(150px,15vw,190px);
  border-bottom:1px solid var(--hair);
}
/* .card--plain lägger sin padding på kortet; med bilden kant i kant flyttas
   den till texten i stället. */
.optcard>:not(.optcard__shot){margin-left:clamp(20px,2vw,26px);margin-right:clamp(20px,2vw,26px)}
.optcard>.eyebrow{margin-top:clamp(18px,1.8vw,24px)}
.optcard>*:last-child{margin-bottom:clamp(20px,2vw,26px)}

/* ==========================================================================
   Kakbanner och mätsamtycke
   Markupen byggs av js/site.js och finns inte i sidorna. Två skäl: den skulle
   annars behöva stämplas in i tjugo filer via en ny partial, och den behöver
   inget CSP-undantag så länge den byggs från ett externt skript.

   ATT DEN KRÄVER JAVASCRIPT ÄR INTE ETT PROBLEM HÄR, det är logiken: utan
   javascript laddas heller ingen mätning, och då finns ingenting att fråga om.
   ========================================================================== */
.cc{
  position:fixed;left:0;right:0;bottom:0;z-index:60;
  background-color:var(--surface-raised);
  border-top:1px solid var(--hair);
  box-shadow:0 -8px 30px rgba(6,32,42,.10);
  animation:cc-in .34s var(--ease-out) both;
}
@media (prefers-reduced-motion:reduce){.cc{animation:none}}
@keyframes cc-in{from{transform:translateY(100%)}to{transform:translateY(0)}}
.cc__inner{
  max-width:1220px;margin:0 auto;padding:20px var(--gutter);
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:16px 40px;align-items:center;
}
.cc__title{font-size:17px;font-weight:600;letter-spacing:-.012em;margin:0 0 4px}
.cc__text p{font-size:15px;line-height:1.55;color:var(--ink-muted);margin:0;max-width:68ch;text-wrap:pretty}
.cc__text a{color:var(--brand);text-underline-offset:3px}
.cc__actions{display:flex;flex-wrap:wrap;gap:10px;justify-content:flex-end}
@media (max-width:760px){
  .cc__inner{grid-template-columns:minmax(0,1fr);gap:14px}
  .cc__actions{justify-content:stretch}
  .cc__actions .btn{flex:1 1 auto;justify-content:center}
}
/* Panelen ligger i samma låda och inte i en egen dialog: valet och texten som
   förklarar det ska kunna läsas samtidigt. */
.cc__prefs{border-top:1px solid var(--hair);background-color:var(--surface)}
.cc__prefs[hidden]{display:none}
.cc__prefsinner{max-width:1220px;margin:0 auto;padding:18px var(--gutter) 22px}
.cc__row{
  display:grid;grid-template-columns:auto minmax(0,1fr);gap:4px 14px;
  padding:14px 0;border-bottom:1px solid var(--hair);
}
.cc__row:last-of-type{border-bottom:0}
.cc__row input{grid-row:1 / span 2;align-self:start;margin-top:3px;width:18px;height:18px;accent-color:var(--brand)}
.cc__row input:disabled{opacity:.55}
.cc__row b{font-size:15px;font-weight:600}
.cc__row span{font-size:14px;line-height:1.5;color:var(--ink-muted);text-wrap:pretty}
.cc__save{margin-top:16px;display:flex;gap:10px;flex-wrap:wrap}
/* Fotens länk tillbaka in i valet. Ett samtycke som inte går att ändra är
   inget samtycke. */
.cc-open{background:none;border:0;padding:0;font:inherit;color:inherit;cursor:pointer;text-decoration:underline;text-underline-offset:3px}

/* --- Bokningsformuläret i två steg -----------------------------------------
   Åtta fält på en gång är den vanligaste anledningen att stänga ett
   offertformulär. Snittet ligger där formuläret ändå byter ämne: huset först,
   du sedan.

   ALLT HÄNGER PÅ .js-KLASSEN. Den sätts av ett inline-skript i <head> innan
   sidan ritas, så steg två är dolt från första bildrutan och blinkar aldrig
   förbi. Utan javascript matchar ingenting nedan: båda stegen syns, nästa- och
   tillbaka-knapparna är dolda, och det är ett vanligt formulär som skickar som
   förut. Det är ordningen som gör det säkert — hade markupen burit hidden hade
   halva formuläret varit oåtkomligt utan javascript. */
.formstep__nav,.formstep__back{display:none}
.js .formstep__nav{display:flex;flex-wrap:wrap;align-items:center;gap:12px 18px;margin-top:26px}
.js .formstep__back{display:inline-flex}
.formstep__count{color:var(--ink-faint)}
.js .quoteform[data-steg="1"] [data-steg-panel="2"],
.js .quoteform[data-steg="2"] [data-steg-panel="1"]{display:none}
/* Steg två kommer in från höger, en gång och kort. Rörelsen säger att man
   flyttat sig framåt i samma formulär och inte bytt sida. */
.js .quoteform[data-steg="2"] [data-steg-panel="2"]{animation:steg-in .28s var(--ease-out) both}
@keyframes steg-in{from{opacity:0;transform:translateX(12px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){
  .js .quoteform[data-steg="2"] [data-steg-panel="2"]{animation:none}
}

/* ==========================================================================
   Webbutiken — .shopmark, .shopgrid, .shopcard
   Fristående block. Ingenting här rör resten av sajten: butikens sidor bär
   klassen .page-butik på <body> och sitt eget sidhuvud från
   partials/header-butik.html, allt annat är sajtens vanliga komponenter.

   FORMSPRÅKET ÄR AVSIKTLIGT OFÖRÄNDRAT. Julius bad om att "allt ändras" när
   man går in i butiken, men i samma stil. Det som ändras är navigeringen och
   sidhuvudets skylt; färger, typsnitt, ytor, hårlinjer och radier är sajtens
   egna. En butik som byter utseende läser som en annan leverantörs sajt, och
   det är precis vad den inte får göra: den ska bära samma varumärke.
   ========================================================================== */

/* Skylten bredvid logotypen. Inte en länk: den svarar på var man är, den tar
   en ingenstans. Hårlinjen till vänster gör den till en fortsättning på
   logotypen i stället för till en fristående etikett. */
.shopmark{
  flex:none;align-self:center;
  padding-left:14px;margin-left:-10px;
  border-left:1px solid var(--hair);
  font-family:var(--fig);font-size:12px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-faint);white-space:nowrap;
}

/* "Hem" är den enda länken i baren som lämnar butiken, så den bär en pil som
   pekar tillbaka. Pilen är ett tecken och inte sprite-symbolen #arrow: den
   symbolen är diagonal och pekar upp-höger, och roterad ett halvt varv pekar
   den ned-vänster, vilket inte betyder "tillbaka" någonstans.

   Väljaren måste vara lika djup som .site-nav > ul > li > a, som sätter
   display:block på varje länk i baren. Grundare vann den regeln, och pilen
   lade sig på egen rad under ordet. */
.site-nav > ul > li > .site-nav__home{display:inline-flex;align-items:center;gap:7px}
.site-nav > ul > li > .site-nav__home::before{
  content:"←";
  font-size:15px;line-height:1;color:var(--ink-faint);
  transition:transform .25s var(--ease-out),color .25s ease;
}
.site-nav > ul > li > .site-nav__home:hover::before{transform:translateX(-3px);color:var(--ink)}
@media (prefers-reduced-motion:reduce){
  .site-nav > ul > li > .site-nav__home:hover::before{transform:none}
}

@media (max-width:1100px){
  /* Skylten är det första som får gå när baren blir trång: kategorierna och
     hemvägen är funktion, skylten är upplysning. */
  .shopmark{display:none}
}

/* --- Rutnätet ------------------------------------------------------------
   auto-fill och inte fasta spalter: sortimentet är sex batterier i dag och
   kan bli fler eller färre utan att någon behöver räkna om ett kolumntal. */
.shopgrid{
  display:grid;gap:clamp(16px,1.8vw,24px);
  grid-template-columns:repeat(auto-fill,minmax(min(100%,260px),1fr));
  margin:0;padding:0;list-style:none;
}

.shopcard{
  display:flex;flex-direction:column;
  background-color:var(--surface-raised);
  border:1px solid var(--hair);border-radius:var(--radius);
  overflow:hidden;
}
/* Produktbilderna är tagna mot olika bakgrunder och i olika proportioner. En
   fast ruta med contain, inte cover: ett batteriskåp som beskärs i underkant
   säljer sämre än ett som får plats. Botten är sajtens sänkta yta, så de sex
   olika fotografierna läser som ett sortiment. */
.shopcard__media{
  display:grid;place-items:center;
  height:200px;padding:20px;
  background-color:var(--surface-raised);
  border-bottom:1px solid var(--hair);
}
/* Måtten sitter på BILDEN och inte som procent mot rutan. max-height:100% på
   ett rutnätsbarn som centreras med place-items har ingen bestämd höjd att
   räknas mot — procenten blev "none" och bilderna renderade 222 px i en ruta
   på 160, alltså rakt ned i kortets text. Rutan är 200 px hög med 20 px luft,
   så innehållsytan är 160: det talet står här i stället för att härledas.
   object-fit:contain gör resten, och eftersom filerna har vit inbränd botten
   och rutan är vit syns ingen brevlåda. */
.shopcard__media img{
  width:100%;height:160px;
  object-fit:contain;object-position:center;
}
/* CheckWatts fil är ett VITT märke på genomskinlig botten, samma sort som
   Trina och de andra i leverantörsraden. På kortets vita ruta syns den inte
   alls — rutan såg tom ut. Maskad och fylld med --brand i stället, exakt
   samma grepp som .supplier__mark. Måtten är filens egen proportion:
   844/167 vid 44 px höjd ger 222. */
.shopcard__mark{
  display:block;background-color:var(--brand);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
}
.shopcard__mark--checkwatt{
  height:44px;width:222px;
  -webkit-mask-image:url("../assets/img/logo-checkwatt.svg");
  mask-image:url("../assets/img/logo-checkwatt.svg");
}
.shopcard__body{display:flex;flex-direction:column;flex:1;padding:18px 20px 20px}
.shopcard__brand{
  font-family:var(--fig);font-size:11.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--ink-faint);
}
.shopcard__name{
  margin:6px 0 0;
  font-size:var(--fs-h3);font-weight:600;letter-spacing:-.022em;line-height:1.25;
  color:var(--ink);
}
.shopcard__text{
  margin:10px 0 0;
  font-size:14.5px;line-height:1.55;color:var(--ink-muted);text-wrap:pretty;
}
/* margin-top:auto trycker foten till kortets underkant, så priset och knappen
   ligger på samma rad i alla kort oavsett hur lång texten ovanför är. */
.shopcard__foot{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  margin-top:auto;padding-top:16px;
}
.shopcard__price{
  font-family:var(--sans);font-size:15px;font-weight:600;
  letter-spacing:-.01em;color:var(--ink);
}
/* PLATSHÅLLARE, inte pris. Sajtens hela argument är att inget pris lämnas
   innan anläggningen är räknad, och det finns ingen prislista i repot. Ett
   påhittat tal här hade varit ett prispåstående, och bevisbördan ligger på
   oss. Den streckade underkanten är samma markering som .bmark-ph i
   hero-raden, så en oifylld plats ser likadan ut överallt på sajten. */
.shopcard__price--ph{
  color:var(--ink-faint);font-weight:500;
  border-bottom:1px dashed var(--accent);padding-bottom:1px;
}

/* --- Ingången ------------------------------------------------------------ */
.shopentry{
  display:grid;gap:clamp(16px,2vw,24px);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,280px),1fr));
  margin:0;padding:0;list-style:none;
}
.shopentry a{
  display:flex;flex-direction:column;height:100%;
  padding:clamp(22px,2.4vw,30px);
  background-color:var(--surface-raised);
  border:1px solid var(--hair);border-radius:var(--radius);
  text-decoration:none;color:inherit;
  transition:border-color .25s ease,transform .25s var(--ease-out);
}
.shopentry a:hover{border-color:var(--brand-100);transform:translateY(-2px)}
@media (prefers-reduced-motion:reduce){.shopentry a:hover{transform:none}}
.shopentry h3{margin:0;font-size:var(--fs-h3);font-weight:600;letter-spacing:-.022em}
.shopentry p{margin:10px 0 0;font-size:15px;line-height:1.55;color:var(--ink-muted);text-wrap:pretty}
.shopentry .link-arrow{margin-top:18px}

/* Raden under rutnätet: hur ett köp faktiskt går till. Den är inte dekor.
   Butiken tar inga betalningsuppgifter och har ingen kassa, och det ska stå
   innan någon klickar, inte efter. */
.shopnote{
  max-width:66ch;margin-top:clamp(24px,3vw,36px);
  font-size:14px;line-height:1.65;color:var(--ink-faint);text-wrap:pretty;
}
.shopnote a{color:var(--ink-muted)}

/* ==========================================================================
   Adressförslag på boka.html — .ac
   Fristående block. Allt inuti byggs av js/adress.js; utan javascript finns
   ingen .ac på sidan alls och fältet är det vanliga textfältet.
   ========================================================================== */
.ac{position:relative}

/* Listan hänger under fältet och lyfts ur flödet, så formuläret inte hoppar
   när förslagen kommer. z-index över kortets egen yta men under sidhuvudet. */
.ac__list{
  position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:20;
  margin:0;padding:6px;list-style:none;
  max-height:284px;overflow-y:auto;overscroll-behavior:contain;
  background-color:var(--surface-raised);
  border:1px solid var(--hair);border-radius:var(--radius-sm);
  box-shadow:0 18px 40px -22px rgba(1,44,60,.45);
}
.ac__item{
  padding:9px 11px;border-radius:calc(var(--radius-sm) - 1px);
  font-size:15px;line-height:1.4;color:var(--ink);
  cursor:pointer;text-wrap:pretty;
}
/* Tangentbord och pekare markerar på samma sätt. aria-selected är sanningen;
   :hover speglar den bara, så det aldrig ser markerat ut på två ställen. */
.ac__item[aria-selected="true"]{background-color:var(--surface-sunken)}
@media (hover:hover){
  .ac__item:hover{background-color:var(--surface-sunken)}
}

/* ==========================================================================
   Butikens produktsidor och varukorg
   Fristående block. Rör ingenting utanför .page-butik.
   ========================================================================== */

/* Varukorgslänken i butikens bar. Siffran är en egen liten bricka och inte en
   parentes i texten: den ska gå att se i ögonvrån utan att läsas. */
.site-nav > ul > li > .site-nav__korg{display:inline-flex;align-items:center;gap:7px}
/* [hidden] MÅSTE STÅ HÄR. Attributet ger display:none via webbläsarens egen
   stilmall, och varje display i en författarregel vinner över den. Utan raden
   visade brickan en nolla i stället för att försvinna när korgen var tom, och
   en tom cirkel med en nolla i ser ut som ett fel. */
.site-nav__korg-n[hidden]{display:none}
.site-nav__korg-n{
  display:inline-grid;place-items:center;
  min-width:19px;height:19px;padding:0 5px;border-radius:999px;
  background-color:var(--accent);color:var(--ink);
  font-family:var(--fig);font-size:11px;line-height:1;font-weight:500;
}

/* Brödsmulan. Butiken är tre nivåer djup och baren visar bara två av dem. */
.prodcrumb{
  padding:18px 0 0;
  font-size:13.5px;color:var(--ink-faint);
}
.prodcrumb a{color:var(--ink-muted);text-decoration:none}
.prodcrumb a:hover{color:var(--ink);text-decoration:underline;text-underline-offset:3px}
.prodcrumb span[aria-hidden]{margin:0 7px;opacity:.5}

/* --- Produktens topp ----------------------------------------------------- */
.prodtop{display:grid;gap:clamp(24px,3.4vw,48px)}
@media (min-width:861px){
  /* Bilden får den mindre halvan. Köprutan är det man kom hit för; produkten
     är redan känd, annars hade man inte klickat in på den. */
  .prodtop{grid-template-columns:minmax(0,.9fr) minmax(0,1fr);align-items:start}
}
.prodtop__media{
  display:grid;place-items:center;margin:0;padding:clamp(24px,3vw,40px);
  background-color:var(--surface-raised);
  border:1px solid var(--hair);border-radius:var(--radius);
}
/* Måtten sitter på bilden, inte som procent mot rutan: samma skäl som i
   .shopcard__media, se noten där. */
.prodtop__media img{width:100%;height:clamp(220px,26vw,340px);object-fit:contain}

/* --- Köprutan ------------------------------------------------------------ */
.buybox{margin-top:clamp(20px,2.4vw,28px)}
.buybox__row{margin-top:16px}
.buybox__row .input{width:100%}
.buybox__row--qty{max-width:140px}
.buybox__price{
  margin-top:20px;
  font-family:var(--sans);font-size:clamp(21px,2vw,26px);font-weight:600;
  letter-spacing:-.02em;color:var(--ink);
}
.buybox__add{margin-top:18px}
@media (max-width:560px){.buybox__add{width:100%;justify-content:center}}
.buybox__note{
  max-width:52ch;margin-top:14px;
  font-size:13.5px;line-height:1.6;color:var(--ink-faint);text-wrap:pretty;
}
.buybox__kvitto{
  margin-top:16px;padding:12px 14px;border-radius:var(--radius-sm);
  background-color:var(--surface-sunken);
  font-size:14.5px;line-height:1.5;color:var(--ink);
}
.buybox__kvitto a{color:var(--brand);font-weight:500}

/* --- Varukorgen ---------------------------------------------------------- */
.korg{margin:0;padding:0;list-style:none;border-top:1px solid var(--hair)}
.korg__rad{
  display:grid;grid-template-columns:auto minmax(0,1fr) auto;
  gap:16px;align-items:center;
  padding:18px 0;border-bottom:1px solid var(--hair);
}
.korg__bild{
  width:72px;height:72px;object-fit:contain;
  background-color:var(--surface-raised);
  border:1px solid var(--hair);border-radius:var(--radius-sm);padding:6px;
}
.korg__namn{margin:0;font-size:16.5px;font-weight:600;letter-spacing:-.015em}
.korg__variant{margin:3px 0 0;font-size:14px;color:var(--ink-muted)}
.korg__styr{display:flex;align-items:center;gap:16px}
.korg__antal{width:74px;text-align:center}
.korg__pris{margin:0;font-size:15.5px;font-weight:600;white-space:nowrap}
.korg__bort{
  padding:0;border:0;background:none;cursor:pointer;
  font-size:13.5px;color:var(--ink-faint);
  text-decoration:underline;text-underline-offset:3px;
}
.korg__bort:hover{color:var(--ink)}
@media (max-width:640px){
  /* Bilden och texten på rad ett, styrningen på rad två: tre kolumner på en
     telefon gör antalsrutan 40 px bred och priset radbrutet mitt i talet. */
  .korg__rad{grid-template-columns:auto minmax(0,1fr)}
  .korg__styr{grid-column:1/-1;justify-content:space-between}
}
.korg__summa{
  display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  margin:20px 0 0;padding-top:4px;
}
.korg__summa-lbl{font-size:15px;color:var(--ink-muted)}
.korg__summa-fig{
  font-family:var(--sans);font-size:clamp(20px,1.8vw,24px);font-weight:600;
  letter-spacing:-.02em;color:var(--ink);
}
.page-butik .quoteform{margin-top:clamp(28px,3.4vw,44px)}
