/* NK Solutions — mörk, exakt, typografisk. */

/* Typsnitten ligger på vår egen server i stället för att hämtas från
   Google Fonts. Varje hämtning därifrån skickar besökarens IP-adress till
   Google — det kräver samtycke enligt GDPR (och har gett böter i EU). Egna
   filer är dessutom snabbare: ingen extra uppkoppling mot en annan domän.
   Bara den latinska delmängden (å, ä, ö, tankstreck …) — samma som Google
   levererade till svenska sidor. Alla tre har fri licens (SIL OFL). */
@font-face {
  font-family: "Inter"; font-style: normal; font-weight: 200 800; font-display: swap;
  src: url("../assets/fonts/inter-var.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: "Instrument Serif"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("../assets/fonts/instrument-serif.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: "Instrument Serif"; font-style: italic; font-weight: 400; font-display: swap;
  src: url("../assets/fonts/instrument-serif-italic.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: "JetBrains Mono"; font-style: normal; font-weight: 400 500; font-display: swap;
  src: url("../assets/fonts/jetbrains-mono-var.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;
}

:root {
  --paper: #0a0a0b;
  --ink: #ecebe7;
  --mute: #8b8983;
  --line: rgba(236, 235, 231, .12);
  --soft: #151517;
  --live: #3f8f5a;
  --serif: "Instrument Serif", "Georgia", serif;
  --sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  --pad: clamp(20px, 5vw, 72px);
  --ease: cubic-bezier(.22, 1, .36, 1);
  /* En skärmhöjdsprocent i pixlar, låst när sidan laddas (skriptet i head).
     Används i stället för vh/svh/lvh överallt. Flera telefonwebbläsare
     ändrar sidans fönsterhöjd — och därmed ALLA viewport-enheter — när
     deras verktygsfält fälls in och ut under scrollen. Då ändrades
     previewkortet (5,2 skärmhöjder) och manifestet (2,7) med ~1 000px mitt
     i en scroll, allt nedanför hoppade, och hero och bakgrund skalades om.
     Låst höjd = inget av det. Räknas bara om när bredden ändras (vridning)
     eller, på dator, när fönstret ändrar storlek. 1vh är reserv utan JS. */
  --vh: 1vh;
  color-scheme: dark;
}

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--paper); color: var(--ink);
  font: 400 16px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "ss01", "cv11";
}
img { max-width: 100%; display: block; height: auto; }
a { color: inherit; text-decoration: none; }
h1, h2, h3 { margin: 0; font-weight: 400; }
p, dl, dd, ol { margin: 0; padding: 0; }
[hidden] { display: none !important; }
::selection { background: var(--ink); color: var(--paper); }

/* ---------- Muspekare ----------
   Själva pekaren byts ut mot en egen bild — inte ett element som jagar
   musen. Den kan därför aldrig släpa efter och kostar ingenting att
   rita. Ljus med mörk kontur, så den syns både mot den mörka sidan och
   mot de ljusa skärmdumparna. Klickbart får samma form i rött, så
   anvisningen finns kvar även utan systemets hand. Bara där det finns en
   riktig pekare — touch har ingen. */
@media (pointer: fine) {
  body { cursor: url("../assets/cursor.svg") 2 2, auto; }
  a, button, summary, label, [role="button"] { cursor: url("../assets/cursor-link.svg") 2 2, pointer; }
}

/* Kornighet — knappt synlig, men ytan känns fysisk, inte renderad. */
.grain {
  position: fixed; inset: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 2; opacity: .06; mix-blend-mode: screen;
}
/* Inte på telefon. Ett helskärmsstort fixed lager med både blend-läge och
   SVG-filter tvingar om-komposition av hela sidan varje scrollbildruta, och
   när adressfältet glider in och ut byter det dessutom storlek så att
   mönstret ritas om över allt — det syns som att sidan skakar och skalas
   om. Kornigheten är en hårfin detalj; den är inte värd det på en liten
   skärm. */
@media (max-width: 760px) { .grain { display: none; } }

.progress { position: fixed; top: 0; left: 0; right: 0; height: 1px; z-index: 60; }
/* Skalas i stället för att byta bredd: en ändrad bredd räknar om layouten
   vid varje scrollhändelse, en skalning är ren GPU-flytt. */
.progress span { display: block; height: 100%; background: var(--ink); transform: scaleX(0); transform-origin: left; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ---------- Bakgrund: flytande grafit ----------
   En WebGL-yta (js/bg.js) som ligger fast bakom hela sidan. Den syns bara
   genom sektionerna utan egen bakgrund — hero, manifest och kontakt — resten
   har en heltäckande bakgrund och skymmer den. Låst höjd (--vh): ytan får
   inte byta storlek när adressfältet på telefon glider in och ut — då
   ritades den om i nya proportioner och såg ut att skalas om. På pekskärm
   är den en fjärdedel högre än skärmen, så den täcker även när
   verktygsfälten fällts in och den synliga ytan blivit högre. */
.bg {
  position: fixed; top: 0; left: 0; z-index: 0; display: block;
  width: 100%; height: calc(var(--vh) * 100);
  pointer-events: none; opacity: 0; transition: opacity 1.8s var(--ease);
}
/* bg-ready sätts av bg.js när första bilden är ritad — ytan startar först
   efter att sidan laddat, och ska inte tonas in innan den har något att visa. */
html.bg-on.bg-ready .bg { opacity: 1; }
@media (pointer: coarse) { .bg { height: calc(var(--vh) * 125); } }
html.no-webgl .bg { display: none; }
main, .footer { position: relative; z-index: 1; }

/* ---------- Intro ----------
   Syns bara med html.is-intro (sätts i head). Loggan ritas som linjer, snittet
   blixtrar till, formerna fylls och undertiteln tonar in — allt i CSS
   nedan. main.js tar över vid ~1,9 s och flyger loggan upp till navet. */
.intro { display: none; }
html.is-intro .intro { display: block; position: fixed; inset: 0; z-index: 100; }
.intro__bg { position: absolute; inset: 0; background: #050506; }
.intro__mark {
  position: absolute; top: 0; left: 0; right: 0; height: calc(var(--vh) * 100); display: grid; place-content: center; justify-items: center;
  gap: clamp(22px, 3vw, 32px);
}
.intro__logo { display: block; width: clamp(120px, 15vw, 190px); height: auto; overflow: visible; will-change: transform; }
.intro__shapes { transform-box: fill-box; transform-origin: center; animation: nk-pop 1.4s var(--ease) .95s both; }
.intro__shapes polygon {
  fill: var(--ink); fill-opacity: 0; stroke: var(--ink); stroke-width: 2.2; stroke-linejoin: miter;
  stroke-dasharray: 1 1; stroke-dashoffset: 1;
  animation: nk-draw 1s cubic-bezier(.65, 0, .35, 1) .12s forwards, nk-fill .5s ease-out 1.05s forwards;
}
.intro__shapes polygon:nth-child(2) { animation-delay: .22s, 1.08s; }
.intro__shapes polygon:nth-child(3) { animation-delay: .3s, 1.11s; }
.intro__shapes polygon:nth-child(4) { animation-delay: .38s, 1.14s; }
.intro__cut {
  fill: none; stroke: #fff; stroke-width: 3; stroke-linecap: round;
  stroke-dasharray: .3 1.6; stroke-dashoffset: .3; opacity: 0;
  animation: nk-cut .6s cubic-bezier(.5, 0, .2, 1) .82s forwards;
}
.intro__tag {
  font: 500 11px/1 var(--mono); letter-spacing: .5em; margin-right: -.5em;
  text-transform: uppercase; color: rgba(236, 235, 231, .72); opacity: 0;
  animation: nk-tag 1s var(--ease) 1.1s forwards;
}
@keyframes nk-draw { to { stroke-dashoffset: 0; } }
@keyframes nk-fill { to { fill-opacity: 1; stroke-opacity: 0; } }
@keyframes nk-cut {
  0% { stroke-dashoffset: .3; opacity: 0; }
  20% { opacity: 1; }
  100% { stroke-dashoffset: -1.6; opacity: 0; }
}
@keyframes nk-pop { 0% { transform: scale(.965); } 100% { transform: none; } }
@keyframes nk-tag { from { opacity: 0; letter-spacing: .9em; } to { opacity: 1; letter-spacing: .5em; } }

/* ---------- Nav ---------- */
.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  padding: 16px var(--pad);
  font-size: 13px; letter-spacing: .01em; color: var(--ink);
  border-bottom: 1px solid transparent;
  transition: background-color .5s var(--ease), border-color .5s var(--ease);
}
/* Genomskinligt över bakgrunden högst upp, en mörk frostad list så fort man
   scrollat (main.js sätter .is-scrolled). */
.nav.is-scrolled {
  background: rgba(10, 10, 11, .74); border-bottom-color: var(--line);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
}
/* Telefon: ingen frostning. En suddig yta över innehåll som scrollar måste
   räknas om varje bildruta, och på iPhone flimrar den dessutom över bilder —
   det var en del av ryckigheten längre ned på sidan. Nästan heltäckande
   mörkt ser likadant ut. */
@media (pointer: coarse) {
  .nav.is-scrolled { background: rgba(10, 10, 11, .95); -webkit-backdrop-filter: none; backdrop-filter: none; }
}
.nav__mark { justify-self: start; display: inline-flex; align-items: center; gap: 12px; }
.nav__logo { display: block; width: 36px; height: auto; fill: var(--ink); }
.nav__meta { display: flex; gap: 18px; align-items: center; font-variant-numeric: tabular-nums; color: var(--mute); }
.nav__status { display: inline-flex; align-items: center; gap: 7px; }
.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--live); box-shadow: 0 0 0 0 rgba(63,143,90,.5); animation: pulse 2.4s ease-out infinite; }
.dot--off { background: #6d6c68; animation: none; }
@keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(63,143,90,.45); } 70% { box-shadow: 0 0 0 7px rgba(63,143,90,0); } 100% { box-shadow: 0 0 0 0 rgba(63,143,90,0); } }
.nav__links { justify-self: end; display: flex; align-items: center; gap: clamp(18px, 2.4vw, 32px); }
.nav__link { font-size: 13px; }
/* Rullgardin under Tjänster med alla tjänstesidor. Riktiga länkar som
   alltid finns i koden — Google läser menyn för att förstå sajtens
   struktur, och det är ofta den som blir raden av undersidor under
   sökträffen. Öppnas vid hovring eller tangentbordsfokus; på pekskärm leder
   Tjänster i stället till samlingssidan. */
.nav__drop { position: relative; }
.nav__menu {
  position: absolute; top: calc(100% + 14px); left: -18px; min-width: 230px; margin: 0; padding: 8px 0;
  list-style: none; background: rgba(10, 10, 11, .97); border: 1px solid var(--line); border-radius: 2px;
  opacity: 0; visibility: hidden; transform: translateY(6px);
  transition: opacity .25s var(--ease), transform .25s var(--ease), visibility 0s linear .25s;
}
.nav__menu::before { content: ""; position: absolute; left: 0; right: 0; top: -16px; height: 16px; }
.nav__drop:hover .nav__menu, .nav__drop:focus-within .nav__menu {
  opacity: 1; visibility: visible; transform: none; transition-delay: 0s;
}
.nav__menu a { display: block; padding: 9px 18px; font-size: 13.5px; opacity: .78; transition: opacity .2s, background-color .2s; }
.nav__menu a:hover, .nav__menu a:focus-visible { opacity: 1; background: rgba(236, 235, 231, .05); }
.nav__menu li:last-child a { margin-top: 6px; padding-top: 12px; border-top: 1px solid var(--line); font: 500 11px/1.4 var(--mono); letter-spacing: .16em; text-transform: uppercase; }
@media (hover: none) { .nav__menu { display: none; } }
@media (max-width: 640px) { .nav__links .nav__about { display: none; } }
.nav__cta {
  display: inline-flex; align-items: center; gap: 8px; padding: 10px 14px; border-radius: 2px;
  background: var(--ink); color: var(--paper); font-weight: 500;
}
/* Under introt är navet tomt; loggan landar på sin plats och resten tonar in
   efteråt. Loggan byts ut i samma bildruta som den flygande landar, så den
   har ingen övergång. */
.nav__meta, .nav__links { transition: opacity 1s var(--ease) .15s; }
html.is-intro .nav__logo, html.is-intro .nav__meta, html.is-intro .nav__links { opacity: 0; }
@media (max-width: 640px) {
  .nav { grid-template-columns: 1fr auto; }
  .nav__meta { display: none; }
}

/* ---------- Typo-verktyg ---------- */
.label {
  font: 500 11.5px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--mute);
}
/* Knappen själv står helt stilla vid hover. Det enda som rör sig är pilen
   bredvid texten, som glider en aning åt höger. */
.arrow { position: relative; display: inline-flex; align-items: center; gap: 8px; font-weight: 500; font-size: 15px; }
.arrow::after, .nav__cta::after, .btn::after { content: "→"; display: inline-block; transition: transform .35s var(--ease); }
.arrow:hover::after, .nav__cta:hover::after, .btn:hover::after { transform: translateX(5px); }
.arrow::before { content: ""; position: absolute; left: 0; right: 0; bottom: -3px; height: 1px; background: currentColor; transform: scaleX(0); transform-origin: left; transition: transform .45s var(--ease); }
.arrow:hover::before { transform: scaleX(1); }
/* Inramad knapp i samma monospace som etiketterna. Ramen ljusnar vid hover,
   men knappen flyttar sig inte. */
.btn {
  display: inline-flex; align-items: center; gap: 12px; padding: 15px 18px;
  border: 1px solid rgba(236, 235, 231, .26); border-radius: 2px;
  font: 500 11.5px/1 var(--mono); letter-spacing: .18em; text-transform: uppercase;
  transition: border-color .35s var(--ease), background-color .35s var(--ease);
}
.btn:hover { border-color: var(--ink); background: rgba(236, 235, 231, .05); }

/* Radvis maskad in-tonning av rubriker.
   Masken är en clip-path med NEGATIV inset: den sträcker sig utanför
   radboxen så att prickarna på Ä/Å/Ö (ovanför) och svansarna på g, p, j
   (nedanför) får plats — med line-height nära 1 hamnar de annars utanför
   boxen och klipps. Till skillnad från padding + negativ margin rubbar den
   ingen layout och krockar inte med margin-kollaps mellan raderna.
   Uppglidningen måste därför starta längre ner än 100 %, annars skulle
   överkanten av bokstäverna titta fram genom den utvidgade masken. */
.line { display: block; clip-path: inset(-.2em 0 -.14em); }
.line > span { display: block; transform: translateY(140%); transition: transform 1.1s var(--ease); }
.in .line > span, .hero.in .line > span { transform: none; }

.reveal { opacity: 0; transform: translateY(22px); transition: opacity 1s var(--ease), transform 1s var(--ease); }
.reveal.in { opacity: 1; transform: none; }

/* ---------- Hero ---------- */
.hero {
  position: relative; min-height: calc(var(--vh) * 100); padding: 128px var(--pad) 36px;
  display: grid; grid-template-rows: 1fr auto; gap: 40px;
}
.hero__body { align-self: center; }
.hero__title {
  font-family: var(--sans); text-transform: uppercase;
  font-size: clamp(46px, 8.4vw, 156px); line-height: .9; letter-spacing: -.035em;
}
.hero__eyebrow {
  display: block; margin-bottom: clamp(20px, 2.6vw, 34px);
  font: 500 12px/1.4 var(--mono); letter-spacing: .24em; color: var(--mute);
}
.hero__thin { font-weight: 200; }
.hero__bold { font-weight: 800; }
.hero__thin > span { transition-delay: .08s; }
.hero__bold > span { transition-delay: .16s; }
.hero__lead {
  margin-top: clamp(28px, 3.6vw, 44px); max-width: 48ch;
  font: 400 12.5px/1.8 var(--mono); letter-spacing: .04em; text-transform: uppercase; color: var(--mute);
}
.hero__links { margin-top: 32px; display: flex; flex-wrap: wrap; align-items: center; gap: 28px; }
.hero__scroll {
  display: flex; align-items: center; gap: 14px;
  font: 500 10.5px/1 var(--mono); letter-spacing: .24em; text-transform: uppercase; color: var(--mute);
}
.hero__scroll i { position: relative; width: 64px; height: 1px; overflow: hidden; background: var(--line); }
.hero__scroll i::after {
  content: ""; position: absolute; inset: 0; background: var(--ink);
  transform: translateX(-100%); animation: scroll-hint 2.6s var(--ease) infinite;
}
@keyframes scroll-hint { 0% { transform: translateX(-100%); } 55%, 100% { transform: translateX(100%); } }
/* Brödtext, knappar och scrollpilen tonar in efter rubriken. */
.hero__lead, .hero__links, .hero__scroll { opacity: 0; transform: translateY(14px); transition: opacity 1s var(--ease), transform 1s var(--ease); }
.hero.in .hero__lead { opacity: 1; transform: none; transition-delay: .35s; }
.hero.in .hero__links { opacity: 1; transform: none; transition-delay: .45s; }
.hero.in .hero__scroll { opacity: 1; transform: none; transition-delay: .7s; }
@media (max-width: 720px) {
  .hero { padding-top: 104px; }
  /* Rubriken bryts till ett ord per rad — större och tyngre än att klämma
     in "EFFEKTIV VARDAG." på en rad i 35px. */
  .hero__title { font-size: clamp(46px, 15vw, 66px); }
}

/* ---------- Manifest ----------
   Sektionen är drygt tre skärmhöjder hög; texten ligger fast i mitten
   under tiden. main.js delar texten i ord (.w) och sätter --p (0–1) per ord
   utifrån hur långt man scrollat: ordet fylls från vänster, de fetade (.k)
   i vitt. Ofyllt är bara konturen. */
.manifest { position: relative; height: calc(var(--vh) * 320); }
.manifest__stage {
  position: sticky; top: 0; height: calc(var(--vh) * 100);
  display: flex; flex-direction: column; justify-content: center;
  padding: 96px var(--pad) 48px;
}
/* Tonar ut texten mot nästa sektion, som har egen, heltäckande bakgrund. */
.manifest::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: calc(var(--vh) * 45); pointer-events: none;
  background: linear-gradient(to bottom, rgba(10, 10, 11, 0), var(--paper) 88%);
}
.manifest__head {
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 16px 24px;
  margin-bottom: clamp(28px, 4vw, 56px);
  font: 500 11px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--mute);
}
.manifest__label { display: flex; align-items: center; gap: 18px; font: inherit; }
.manifest__label::after { content: ""; width: clamp(40px, 16vw, 280px); height: 1px; background: var(--line); }
.manifest__keys { list-style: none; display: flex; flex-wrap: wrap; gap: 10px clamp(14px, 2vw, 28px); }
.manifest__keys li { display: flex; align-items: center; gap: 10px; opacity: .38; transition: opacity .5s var(--ease), color .5s var(--ease); }
.manifest__keys li::before { content: ""; width: 0; height: 1px; background: currentColor; transition: width .5s var(--ease); }
.manifest__keys li.on { opacity: 1; color: var(--ink); }
.manifest__keys li.now::before { width: 18px; }
.manifest__text {
  font-family: var(--sans); font-weight: 200; text-transform: uppercase;
  font-size: clamp(27px, 4.4vw, 78px); line-height: 1.04; letter-spacing: -.02em; max-width: 19.5em;
}
.manifest__text b { font-weight: 700; }
.w {
  --fill: rgba(236, 235, 231, .6);
  display: inline-block; color: rgba(236, 235, 231, .06);
  -webkit-text-stroke: 1px rgba(236, 235, 231, .26);
  background-image: linear-gradient(90deg, var(--fill) calc(var(--p, 0) * 100%), transparent calc(var(--p, 0) * 100%));
  -webkit-background-clip: text; background-clip: text;
}
.w.k { --fill: #fff; }
@media (max-width: 760px) {
  .manifest { height: calc(var(--vh) * 270); }
  .manifest__head { margin-bottom: 26px; font-size: 10px; letter-spacing: .16em; }
  .manifest__label::after { width: 40px; }
  /* Konturen blir för tunn att läsa i liten storlek — ofyllt är i stället
     en svag fyllning. */
  .w { -webkit-text-stroke: 0; color: rgba(236, 235, 231, .17); }
}

/* ---------- Sektioner ---------- */
/* Heltäckande bakgrund: skymmer den flytande ytan bakom. */
.section { position: relative; background: var(--paper); padding: clamp(72px, 9vw, 128px) var(--pad); border-top: 1px solid var(--line); }
.section__head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: clamp(36px, 5vw, 64px); }
.section__count { font: 500 11.5px/1 var(--mono); letter-spacing: .12em; color: var(--mute); font-variant-numeric: tabular-nums; }

/* ---------- Case ---------- */
.case { display: grid; grid-template-columns: 1.35fr .65fr; gap: clamp(28px, 4vw, 64px); align-items: end; }
.case__zoom { position: relative; }

/* Kortet låser sig mitt i vyn när man scrollat fram till det och ligger
   kvar där medan skärmdumpen bläddrar genom kundens sida (main.js). När
   bläddringen är klar slutar stagen vara sticky och man scrollar vidare
   som vanligt. Höjden på .pin är hela förloppet; en skärmhöjd av den "äts
   upp" av stagen, resten är det man scrollar igenom.

   Alla höjder i låsta skärmhöjder (--vh, se :root), inte vh/svh: i
   webbläsare som ändrar fönsterhöjden med verktygsfälten ändrades hela
   förloppets längd mitt i en scroll, och sidan hoppade.

   --hold-in/--hold-out: previewen står still den första och sista biten av
   förloppet, så snappet hinner landa innan sidan börjar rulla och man
   hinner se att den är slut innan stagen släpper. Andelar av sträckan
   stagen sitter fast (--pin-h − en skärmhöjd). Läses även av main.js. */
.pin { --pin-h: calc(var(--vh) * 375); --hold-in: .14; --hold-out: .78; position: relative; height: var(--pin-h); }
/* padding-top: kortet centreras i ytan UNDER navet, inte i hela fönstret —
   annars la sig navet över toppen av previewen på telefoner, där den
   synliga ytan bara är ~550–660px med webbläsarens fält framme.
   safe center: blir kortet ändå högre än ytan läggs det mot toppen i
   stället för att sticka ut lika mycket åt båda håll. Då är det texten
   längst ner som hamnar utanför en stund, inte previewen. */
.pin__stage {
  position: sticky; top: 0; display: flex; padding-top: 80px;
  align-items: center; align-items: safe center;
  height: calc(var(--vh) * 100);
}
/* Kortet måste få full bredd explicit: previewbilden ligger absolut inuti
   sitt fönster och ger därför ingen egen bredd att fylla ut med, så kortet
   skulle annars krympa ihop till sin textkolumn. */
.pin__stage > .case { width: 100%; }
/* Telefon: sajten i previewen är Björkstadens mobilvy, ~11 000px lång —
   fyra gånger så lång i förhållande till fönstret som desktopsidan. Med
   samma korta förlopp som förut rusade den förbi med ~9px per scrollad
   pixel. Nu är förloppet längre, stillastående början och slut kortare,
   och fönstret högre, så sidan rör sig ungefär lika fort som på datorn.
   Fönstret tar den höjd som blir över när kortets text fått plats under
   navet (kortet måste rymmas på ~550–660px synlig yta), och meta-listan
   (bransch/omfattning/år) göms för att ge det plats — beskrivningen
   ovanför säger samma sak.

   Inget snäpp på telefon. Det fanns snäppunkter som stoppade hårda svep vid
   kortet, med ett snäppläge som slogs om mitt i draget — men att byta
   snäppläge under en pågående scroll får webbläsaren att hoppa till
   närmaste snäppunkt, och på riktiga telefoner syntes det som ryck och
   som att sidan kastades tillbaka. Vanlig scroll, rakt av. */
@media (max-width: 760px) {
  .pin { --pin-h: calc(var(--vh) * 520); --hold-in: .06; --hold-out: .9; }
  .pin .case__screen { aspect-ratio: auto; height: clamp(200px, calc(var(--vh) * 100 - 340px), 460px); }
  .pin .meta { display: none; }
}
/* På pekskärm drivs previewen av webbläsarens egen scroll via en
   scrolltidslinje, inte av JS: den ligger i exakt samma steg som fingret
   och svepets rörelsemängd, utan att vänta på något skript — som en
   riktig sida man scrollar i. (Med JS och eftersläp, som på datorn,
   släpade den efter fingret och hackade när sidan hade annat för sig.)
   Stöds det inte tar main.js över.

   Intervallet i längder från där stagen låser (contain 0 = kortets
   överkant i fönstrets överkant), med den låsta skärmhöjden — inte i
   procent av contain-intervallet, som räknas mot fönstrets aktuella höjd
   och ändras med verktygsfälten. */
@media (pointer: coarse) and (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .pin { view-timeline: --pin block; }
    .case__screen { container-type: size; }
    .case__page-wrap {
      animation: pin-scroll linear both;
      animation-timeline: --pin;
      animation-range:
        contain calc(var(--hold-in) * (var(--pin-h) - var(--vh) * 100))
        contain calc(var(--hold-out) * (var(--pin-h) - var(--vh) * 100));
    }
  }
}
/* -100% är hela sidbildens höjd; + 100cqh (fönstrets höjd) gör att den
   stannar när sidans nederkant når fönstrets nederkant. */
@keyframes pin-scroll { to { transform: translateY(calc(-100% + 100cqh)); } }
@media (prefers-reduced-motion: reduce) {
  /* Ingen fastlåsning vid reducerad rörelse — kortet säger redan allt som
     behövs utan förloppet. */
  .pin { height: auto; }
  .pin__stage { position: static; height: auto; display: block; }
}

.case__media {
  --shot-pad: clamp(18px, 3vw, 44px);
  position: relative; display: block; background: var(--soft); border-radius: 4px;
  padding: var(--shot-pad) var(--shot-pad) 0; overflow: hidden;
}
/* Previewen är en riktig scroll, inte en bildsekvens: .case__screen är
   fönstret och .case__page är HELA sidan som en enda lång bild som flyttas
   med transform inuti det. Därför blir rörelsen steglös och knivskarp —
   det finns inga bildrutor att hoppa mellan och inget att tona över.
   translate3d ger bilden ett eget kompositlager, så flytten blir ren
   GPU-flytt utan omritning. */
.case__screen {
  display: block; position: relative; overflow: hidden;
  aspect-ratio: 1400 / 875; background: var(--paper);
  border-radius: 4px 4px 0 0; box-shadow: 0 30px 60px -30px rgba(0, 0, 0, .45);
  transition: transform 1.2s var(--ease);
}
/* Omslaget är det som flyttas. Sidbilden ligger i flödet inuti det så att
   omslagets höjd blir bildens. Hero-ytan byggs i tre lager exakt där videon
   sitter på riktiga sajten (helbredd högst upp, 1400x918 vid fångstbredden):
   sidbilden underst, den levande videon ovanpå, och överst en genomskinlig
   bild med allt som ligger över videon på riktigt — gradient-tinten,
   rubriken, menyn (tools/capture-hero-overlay.mjs). Så rullar filmen på
   riktigt men ytan ser ut precis som sajten. Alla tre följer med när sidan
   rullar och täcks av innehållet nedanför precis som på riktigt. */
.case__page-wrap { position: absolute; top: 0; left: 0; width: 100%; }
.case__page-wrap picture { display: block; }
.case__page { display: block; width: 100%; height: auto; }
.case__hero-video, .case__hero-dim, .case__hero-overlay {
  position: absolute; top: 0; left: 0; display: block;
  width: 100%; aspect-ratio: 1400 / 920;
}
.case__hero-video { object-fit: cover; }
/* Riktiga sajten dämpar filmen med brightness(.55). Samma sak som ett 45 %
   svart lager ovanpå — och ett lager är billigare än ett filter på en video
   som rullar, särskilt på telefon. */
.case__hero-dim { background: rgba(0, 0, 0, .45); }
/* Hero-ytan har helt andra mått i sajtens mobilvy. Måste stå EFTER
   grundregeln ovan — samma specificitet, så den som kommer sist vinner. */
@media (max-width: 760px) {
  .case__hero-video, .case__hero-dim, .case__hero-overlay { aspect-ratio: 430 / 877; }
}
.case__media:hover .case__screen { transform: translateY(-6px); }

.case__title { font-family: var(--serif); font-size: clamp(32px, 3.6vw, 52px); line-height: 1; letter-spacing: -.01em; margin-bottom: 16px; }
.case__desc { color: var(--ink); opacity: .82; margin-bottom: 24px; max-width: 42ch; }
.meta { display: grid; gap: 10px; padding: 18px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); margin-bottom: 22px; font-size: 13.5px; }
.meta div { display: grid; grid-template-columns: 96px 1fr; gap: 12px; }
.meta dt { color: var(--mute); }
@media (max-width: 860px) {
  .case { grid-template-columns: 1fr; align-items: start; }
}

/* ---------- Lista ---------- */
.list { list-style: none; border-top: 1px solid var(--line); }
.list__row {
  display: grid; grid-template-columns: 64px 1fr 1.4fr auto; gap: 24px; align-items: baseline;
  padding: 28px 0; border-bottom: 1px solid var(--line);
}
.list__price {
  font: 500 16px/1.4 var(--sans); white-space: nowrap; font-variant-numeric: tabular-nums;
  color: var(--mute); justify-self: end;
}
.list__price strong { font-weight: 600; color: var(--ink); }
.list__n { font: 500 11.5px/1 var(--mono); letter-spacing: .08em; color: var(--mute); font-variant-numeric: tabular-nums; }
.list__t { font-family: var(--serif); font-size: clamp(26px, 2.6vw, 36px); line-height: 1.05; }
.list__d { color: var(--ink); opacity: .78; max-width: 52ch; }

/* Underpunkter — det som ingår i en hemsida, ett snäpp mindre än raden
   de hör till så hierarkin syns utan att de blir egna rubriker.
   De spänner över BÅDE text- och priskolumnen, så tilläggspriserna hamnar
   vid samma högerkant som radens eget pris i stället för att sluta inne i
   textspalten. Brödtexten hålls kort med egen max-bredd i stället. */
.sub { list-style: none; margin-top: 22px; display: grid; gap: 16px; grid-column: 3 / -1; }
.sub__item { padding-left: 18px; border-left: 1px solid var(--line); }
.sub__head { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; margin-bottom: 4px; }
.sub__t { font: 500 15px/1.3 var(--sans); }
.sub__d { font-size: 14.5px; color: var(--mute); max-width: 52ch; }
/* "Tillägg" står över priset så det aldrig kan läsas som att det ingår. */
.sub__price {
  font: 500 14px/1.3 var(--sans); white-space: nowrap; text-align: right;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.sub__price small {
  display: block; font: 500 9.5px/1 var(--mono); letter-spacing: .18em;
  text-transform: uppercase; color: var(--mute); margin-bottom: 4px;
}

/* Korten glider in ett i taget i stället för allihop på en gång. Bara ett
   par steg — fler och den sista raden hänger kvar för länge. */
.list__row:nth-child(2), .tier:nth-child(2) { transition-delay: .12s; }
.list__row:nth-child(3), .tier:nth-child(3) { transition-delay: .24s; }

@media (max-width: 720px) {
  /* Priset flyttas upp direkt under rubriken i stället för att hamna sist,
     efter underpunkterna, där ingen letar efter det. */
  .list__row { grid-template-columns: 40px 1fr; gap: 12px 24px; }
  .list__price { grid-column: 2; grid-row: 2; justify-self: start; }
  .list__d { grid-column: 2; grid-row: 3; }
  .sub { grid-column: 2; grid-row: 4; }
}

/* ---------- Steg ---------- */
.steps { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: clamp(24px, 3vw, 44px); }
.step { padding-top: 20px; border-top: 1px solid var(--line); }
.step__k { display: block; font: 500 11.5px/1 var(--mono); letter-spacing: .12em; color: var(--mute); margin-bottom: 14px; }
.step__t { font-family: var(--serif); font-size: clamp(22px, 2vw, 28px); line-height: 1.1; margin-bottom: 10px; }
.step p { color: var(--ink); opacity: .78; font-size: 15px; }
@media (max-width: 720px) { .steps { grid-template-columns: 1fr; gap: 28px; } }

/* Noten om moms under tjänstelistan. */
.price__note { color: var(--ink); opacity: .78; max-width: 62ch; margin-top: clamp(28px, 4vw, 40px); }

/* ---------- Systemutveckling & integrationer ---------- */
.tech__intro {
  display: grid; grid-template-columns: 1fr 1fr; gap: clamp(28px, 6vw, 96px);
  margin-bottom: clamp(48px, 7vw, 96px);
}
.tech__lead { font-family: var(--serif); font-size: clamp(28px, 3.2vw, 46px); line-height: 1.1; letter-spacing: -.01em; max-width: 20ch; }
.tech__p { color: var(--ink); opacity: .78; max-width: 56ch; margin-bottom: 18px; }
.tech__body .arrow { margin-top: 8px; }
.tech__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 40px clamp(24px, 3vw, 48px); }
.tech__group { padding-top: 20px; border-top: 1px solid var(--line); }
.tech__group:nth-child(2) { transition-delay: .08s; }
.tech__group:nth-child(3) { transition-delay: .16s; }
.tech__group:nth-child(4) { transition-delay: .24s; }
.tech__t { font: 500 11px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--mute); margin-bottom: 18px; }
.tech__list { list-style: none; margin: 0; padding: 0; }
.tech__list li { padding: 7px 0; font-size: 15px; line-height: 1.4; }
@media (max-width: 960px) {
  .tech__intro { grid-template-columns: 1fr; }
  .tech__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
  .tech__grid { grid-template-columns: 1fr; gap: 32px; }
  .tech__group:nth-child(n) { transition-delay: 0s; }
}

/* ---------- Om ---------- */
.about { display: grid; grid-template-columns: .55fr 1fr; gap: clamp(32px, 6vw, 96px); align-items: center; }
.about__figure { margin: 0; aspect-ratio: 4 / 5; overflow: hidden; border-radius: 4px; background: var(--soft); max-width: 420px; }
/* Svartvitt redan i bildfilen, inte via filter: ett CSS-filter på en bild
   som scrollar under navet flimrade på telefon. */
.about__figure img { width: 100%; height: 100%; object-fit: cover; }
.about__text .label { margin-bottom: 20px; }
.about__p { font-family: var(--serif); font-size: clamp(22px, 2.3vw, 32px); line-height: 1.25; margin-bottom: 18px; max-width: 30ch; }
.about__p a { border-bottom: 1px solid var(--line); }
.about__p--small { font-family: var(--sans); font-size: 15px; color: var(--mute); margin-top: 8px; }
@media (max-width: 760px) { .about { grid-template-columns: 1fr; } .about__figure { max-width: 300px; } }
/* Hundis — bulldoggen i kostym. Ett litet kort under texten: rund bild,
   namnet som etikett och rollen bredvid. */
.buddy { display: flex; align-items: center; gap: 18px; margin: 10px 0 30px; padding-top: 22px; border-top: 1px solid var(--line); max-width: 30rem; }
.buddy img { flex: none; width: 112px; height: 112px; border-radius: 50%; object-fit: cover; background: var(--soft); }
.buddy figcaption { display: grid; gap: 8px; }
.buddy__name { font: 500 11.5px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--ink); }
.buddy__role { font-family: var(--serif); font-size: clamp(19px, 1.7vw, 23px); line-height: 1.2; color: var(--mute); text-wrap: balance; }

/* ---------- Kontakt ---------- */
/* Ingen egen bakgrund: den flytande ytan kommer tillbaka här, som en ram
   runt det sista man ser. Toningen ovanför gör övergången från den
   heltäckande sektionen innan mjuk. */
.contact { position: relative; padding: clamp(110px, 14vw, 200px) var(--pad) clamp(56px, 8vw, 96px); border-top: 1px solid var(--line); }
.contact::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: calc(var(--vh) * 40); pointer-events: none;
  background: linear-gradient(to bottom, var(--paper), rgba(10, 10, 11, 0));
}
.contact > * { position: relative; }
.contact__title { font-family: var(--serif); font-size: clamp(56px, 11vw, 168px); line-height: .95; letter-spacing: -.02em; margin-bottom: clamp(36px, 5vw, 64px); }
.contact__links { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; max-width: 900px; margin-bottom: 32px; }
.contact__link { display: grid; gap: 8px; padding: 22px 0; border-top: 1px solid var(--line); font-family: var(--serif); font-size: clamp(22px, 2.6vw, 34px); line-height: 1; transition: opacity .2s; }
.contact__link:hover { opacity: .6; }
.contact__k { font: 500 11px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--mute); }
.contact__note { color: var(--mute); }
@media (max-width: 600px) { .contact__links { grid-template-columns: 1fr; gap: 0; } }

/* ---------- Undersidor ----------
   Tjänstesidorna, Om, Kontakt och Integritetspolicy (genereras av
   tools/build-pages.py). Samma byggstenar som startsidan; det här är det
   som bara finns där. */
.hero--page { min-height: 0; padding-top: clamp(130px, 14vw, 180px); padding-bottom: clamp(56px, 7vw, 96px); grid-template-rows: auto; }
.hero--page .hero__title { font-size: clamp(42px, 6.6vw, 118px); }
@media (max-width: 720px) { .hero--page .hero__title { font-size: clamp(40px, 12.5vw, 58px); } }
.crumbs { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: clamp(22px, 3vw, 36px); font: 500 11px/1.4 var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--mute); }
.crumbs a { color: var(--ink); opacity: .8; }
.crumbs a:hover { opacity: 1; }
/* Rubrik inne i en sektion — samma serif som resten av sidan. */
.h2 { font-family: var(--serif); font-size: clamp(30px, 3.4vw, 50px); line-height: 1.08; letter-spacing: -.01em; margin-bottom: clamp(24px, 3vw, 40px); max-width: 24ch; }
/* Kort i rutnät: rubrik + text, till exempel "Exempel på uppdrag". */
.tech__group p { color: var(--ink); opacity: .78; font-size: 15px; }
.tech__group h3.tech__t + p { margin-top: -6px; }
/* Prislista: namn och pris på en rad, beskrivning under. */
.prices { margin: 0; border-top: 1px solid var(--line); }
.prices > div { display: grid; grid-template-columns: 1fr auto; gap: 6px 24px; padding: 22px 0; border-bottom: 1px solid var(--line); }
.prices dt { font-family: var(--serif); font-size: clamp(22px, 2.2vw, 30px); line-height: 1.1; }
.prices dd { font: 500 15px/1.4 var(--sans); font-variant-numeric: tabular-nums; white-space: nowrap; justify-self: end; align-self: baseline; }
.prices p { grid-column: 1 / -1; color: var(--ink); opacity: .72; font-size: 15px; max-width: 62ch; }
/* Vanliga frågor: inbyggda <details>, fungerar utan JS och läses av Google. */
.faq { border-top: 1px solid var(--line); max-width: 900px; }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  list-style: none; display: flex; justify-content: space-between; align-items: baseline; gap: 24px;
  padding: 22px 0; font-family: var(--serif); font-size: clamp(20px, 1.9vw, 26px); line-height: 1.2;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; font: 400 20px/1 var(--sans); color: var(--mute); transition: transform .35s var(--ease); }
.faq details[open] summary::after { transform: rotate(45deg); }
.faq details > div { padding: 0 0 24px; max-width: 64ch; color: var(--ink); opacity: .8; }
.faq details > div p + p { margin-top: 12px; }
.faq a, .prose a { border-bottom: 1px solid var(--line); }
/* Fler tjänster: länkkort. */
.related { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 1px; background: var(--line); border: 1px solid var(--line); }
.related a { display: grid; gap: 10px; align-content: start; padding: 26px; background: var(--paper); transition: background-color .35s var(--ease); }
.related a:hover { background: var(--soft); }
.related .card__t { font-family: var(--serif); font-size: 26px; line-height: 1.1; }
.related .card__d { color: var(--ink); opacity: .72; font-size: 14.5px; }
.related .arrow { font-size: 13px; margin-top: 6px; }
/* Löptext (integritetspolicy, kontaktdetaljer). */
.prose { max-width: 70ch; }
.prose h2 { font-family: var(--serif); font-size: clamp(24px, 2.4vw, 32px); line-height: 1.15; margin: 44px 0 14px; }
.prose h2:first-child { margin-top: 0; }
.prose p, .prose li { color: var(--ink); opacity: .82; }
.prose p + p { margin-top: 14px; }
.prose ul { margin: 12px 0 0; padding-left: 20px; }
.prose li + li { margin-top: 6px; }
.prose .muted { color: var(--mute); opacity: 1; font-size: 14px; }
.section + .section--flush { border-top: 0; padding-top: 0; }
/* Läs mer-länkar i tjänstelistan på startsidan. */
.list__more { display: flex; flex-wrap: wrap; gap: 10px 22px; margin-top: 14px; }
/* I tjänstelistans rutnät: under beskrivningen. Priset låses i sin kolumn
   så att länkraden inte knuffar ned det. */
.list__row > .list__price { grid-column: 4; grid-row: 1; }
.list__row > .list__more { grid-column: 3; grid-row: 2; margin-top: 0; }
.list__row > .sub { grid-row: 3; }
@media (max-width: 720px) {
  .list__row > .list__price { grid-column: 2; grid-row: 2; }
  .list__row > .list__more { grid-column: 2; grid-row: 4; }
  .list__row > .sub { grid-row: 5; }
}
.list__more .arrow { font-size: 13.5px; }

/* ---------- Footer ----------
   Länkar till alla sidor: för besökaren, och för Google — det är bland annat
   ur sådana länkar som sökmotorn förstår sidans struktur och kan visa
   undersidor som egna länkar under sökträffen. */
.footer { padding: clamp(48px, 6vw, 80px) var(--pad) 28px; border-top: 1px solid var(--line); color: var(--mute); }
.footer__grid { display: grid; grid-template-columns: 1.4fr repeat(3, 1fr); gap: 36px clamp(24px, 4vw, 64px); margin-bottom: clamp(40px, 5vw, 64px); }
.footer__logo { display: inline-block; margin-bottom: 18px; }
.footer__logo svg { display: block; width: 44px; height: auto; fill: var(--ink); }
.footer__brand p { max-width: 34ch; font-size: 14.5px; line-height: 1.6; }
.footer__h { font: 500 11px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--ink); margin-bottom: 18px; }
.footer__col ul { list-style: none; margin: 0; padding: 0; }
.footer__col li { padding: 5px 0; font-size: 14.5px; }
.footer__col a { color: var(--ink); opacity: .72; transition: opacity .2s; }
.footer__col a:hover { opacity: 1; }
.footer__base {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px 32px;
  padding-top: 22px; border-top: 1px solid var(--line);
  font: 400 11px/1.6 var(--mono); letter-spacing: .08em; text-transform: uppercase;
}
@media (max-width: 860px) { .footer__grid { grid-template-columns: 1fr 1fr; } .footer__brand { grid-column: 1 / -1; } }
@media (max-width: 420px) { .footer__grid { grid-template-columns: 1fr; } }

/* ---------- Tillgänglighet ---------- */
:focus-visible { outline: 1.5px solid var(--ink); outline-offset: 4px; border-radius: 2px; }
/* Sektioner som får fokus när man klickat en länk dit (main.js) ska inte ramas in. */
[tabindex="-1"]:focus { outline: none; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal, .line > span { opacity: 1; transform: none; transition: none; }
  .hero__lead, .hero__links, .hero__scroll { opacity: 1; transform: none; transition: none; }
  .hero__scroll i::after { animation: none; }
  /* Inget fastlåst manifest — texten står färdigifylld. */
  .manifest { height: auto; }
  .manifest__stage { position: static; height: auto; padding-block: clamp(80px, 10vw, 140px); }
  .manifest::after { display: none; }
  .w { --p: 1; }
  .dot { animation: none; }
  .case__screen, .case__media, .arrow { transition: none; }
  .arrow::after, .arrow::before, .nav__cta::after, .btn::after { transition: none; }
}
