/* ── bakgrunn.css — CSS-laget til den delte bakgrunnsmodulen ─────────────────
   PARTNER TIL `bakgrunn.js`. Filene hører sammen og MÅ shippe i samme slag:
   JS-en bygger nodene og kartlerretet, denne fila maler dem.

   ⚠️ HVORFOR DETTE ER EN EGEN FIL OG IKKE EN STRENG I `bakgrunn.js`
   (026 bom-hullet, 2026-07-29 — måling, ikke mening):
   Ved cross-document view transitions tas snapshotet av det NYE dokumentet
   ved FØRSTE RENDER-MULIGHET — mens dokumentet fortsatt er `readyState:
   "loading"` (målt: 6/6 navigasjoner). `bakgrunn.js` kalles fra inline-
   scriptet NEDERST i sidene, og rakk derfor ikke fram: `.bakgrunn` fantes
   IKKE ved snapshot i 4 av 6 målinger, og `leita-bakgrunn` fikk bare en
   `old` — bakgrunnen FADET i stedet for å stå stille, som er hele
   illusjonen 026 er bygget for.
   Et render-blokkerende `<link rel="stylesheet">` i `<head>` er den eneste
   mekanismen som garanterer at reglene GJELDER før første render-mulighet.
   En `<style>` injisert av JS kan per definisjon ikke det.

   ⚠️ SIDENE MÅ HA BÅDE LENKA OG PLASSHOLDER-NODEN:
       <link rel="stylesheet" href="bakgrunn.css" id="bakgrunn-css">   (i <head>)
       <div class="bakgrunn" aria-hidden="true"></div>                 (først i <body>)
   Noden må finnes i HTML-en, ikke bygges av JS: en regel treffer ingen node
   som ikke finnes. `sikreLag()` i bakgrunn.js OVERTAR en eksisterende
   `.bakgrunn` og bygger bare hvis den mangler — begge veier virker, men bare
   plassholder-varianten paret opp 20/20.
   `id="bakgrunn-css"` er ikke pynt: `injiserCss()` i bakgrunn.js sjekker
   nettopp den IDen og hopper over sin egen fallback-injeksjon når den finnes.

   ⚠️ ÉN KILDE. Reglene under sto tidligere som en JS-streng i `bakgrunn.js`;
   de er flyttet ORDRETT hit (bevist: regel-teksten er byte-identisk med den
   `bakgrunn.js` injiserte på d0622c9, kommentarer strippet — sha256
   b8679a58…cedcbb6e5). Ikke lag en kopi tilbake i JS-en; da drifter de to
   fra hverandre, og verdiene under er kalibrert mot hverandre i par.
   ───────────────────────────────────────────────────────────────────────── */

/* ── CSS-laget (B13/R1 + B17) ─────────────────────────────────────────────
   ⚠️ Alle gradient-stopp fader til SAMME farge med alpha 0 — aldri
   `transparent`. `transparent` == `rgba(0,0,0,0)`, så nettleseren
   interpolerer mot SVART og gløden får en grå, skitten dødsone. Det er
   hovedgrunnen til at mørke gradienter leser billig.
   `.grain` ligger OVER gradientene: banding er det som leser billig, ikke
   gradienten selv. */
/* ── B22 — GLØDENS GEOMETRI ER BREAKPOINT-AVHENGIG ────────────────────────
   Rotårsaken fra leveranse 1: verdiene under var kalibrert mot mobilens
   proporsjoner, og desktop ARVET dem.
   `vw` er proporsjonalt likt på tvers av BREDDER, men ikke på tvers av
   ASPEKT — og det er aspektet som snur:
      390×844 (portrett):  125vw = 487 px = 0,58 × høyden  → glød i hjørnet
     1440×900 (landskap):  125vw = 1800 px = 2,0 × høyden  → halvsides vask
   Samme tall, to helt ulike komposisjoner. Derfor er de to flatene skilt:
   smal/portrett beholder `vw`, bred flate måles mot KORTAKSEN (`vmin`), som
   er den aspekt-uavhengige referansen, og strammes ~21 % i tillegg.
   ⚠️ `inset:-20%` gjør at %-posisjonene løper over en boks som er 140 % av
   viewporten: `79% 83%` lander faktisk på (90,6 %, 96,2 %) av viewporten.
   Regn om FØR du «flytter senteret mot hjørnet» — det står der allerede.
   Dybden (tre stablede skalaer) og `.grain` over er uendret i begge: det er
   bandingen som leser billig, ikke gradienten. */
/* ── A2 — BASEN KOMPENSERER FOR KORNETS MIDDEL-LØFT ─────────────────────
   Kornet ligger nå i `mix-blend-mode:normal` (se `.grain` under) og løfter
   derfor nesten-svart med ~+2,6 LSB. Det er UUNNGÅELIG: på nesten-svart kan
   et lag bare LEGGE TIL lys, og forholdet er konstant (~0,92 LSB løft per
   1 LSB amplitude). Løsningen er å senke basen tilsvarende, ikke å senke
   amplituden — amplituden er hele poenget med kornet.
   Målt rendret luminans: #0d0c0b + korn = 14,72 · #0b0a09 + korn = 12,72,
   mot originalens 12,13. Amplituden er uendret.
   ⚠️ JUSTERT DA DET GROVE LAGET (`.bakgrunn::after`) KOM TIL. To støylag
   legger begge lys til nesten-svart, og uten en nedjustering ville
   nesten-svart gått 12,7 → 16,9 — altså MER LYS, som er nøyaktig det Niklas
   ikke ba om. Han ba om mer TEKSTUR ved samme mørke.
   Verdien står nå på `#080807`, som er partneren til opasitet `.02`.
   ⚠️ VERDIEN ER PARET MED `::after`-OPASITETEN. Endrer du den ene, MÅ du
   endre den andre. Tabellen med de kalibrerte parene, og hvorfor pariteten
   finnes, står ved `::after`-opasiteten lenger nede — det er den linja en
   fremtidig justering faktisk treffer.
   ⚠️ Kun `.bakgrunn` sin EGEN `background` senkes. `--bg` og `body`-
   bakgrunnen står urørt — AK17 måler `--bg == #0d0c0b` og body
   `rgb(13,12,11)`. `.bakgrunn` er `position:fixed;inset:0` og dekker body
   fullstendig, så det er denne som faktisk vises. */
.bakgrunn{position:fixed;inset:0;z-index:-1;background:#080807;pointer-events:none;
  view-transition-name:leita-bakgrunn}
  /* ── SMAL / PORTRETTFLATE (≤640 px) — leveranse 1-verdiene, URØRT ── */
.bakgrunn::before{content:"";position:absolute;inset:-20%;
  background:
    radial-gradient(38vw 38vw at 79% 83%,
      rgba(230,125,55,.30) 0%, rgba(230,125,55,.10) 38%, rgba(230,125,55,0) 66%),
    radial-gradient(68vw 66vw at 87% 91%,
      rgba(150,81,44,.26) 0%, rgba(150,81,44,.09) 45%, rgba(150,81,44,0) 72%),
  /* ── A4 — L3s NULL-STOPP FLYTTET FRA 78 % TIL 100 % ───────────────────
     Målt på rendret PNG: i punktene «ytre» og «fjern» var luminansen
     EKSAKT lik basen (12,1404 = ren #0d0c0b, korn-hf 0,000) — de tre
     gradientene bidro NULL der. Alle tre bidrar i sin egen sone, men L3
     ble kappet ved 78 % av radien og etterlot en død ytterflate.
     Kjerne- (0 %) og midt-stoppet (50 %) er URØRT, så den godkjente
     glød-intensiteten står; det er kun halen som forlenges. */
    radial-gradient(125vw 95vw at 72% 104%,
      rgba(71,44,31,.55) 0%, rgba(71,44,31,.18) 50%, rgba(71,44,31,.05) 78%,
      rgba(71,44,31,0) 100%);
  filter:blur(22px) brightness(calc(1 + var(--drag,0) * .45));
  animation:bgPust 26s ease-in-out infinite alternate}
  /* ── ≥641 px: SKILT PÅ ASPEKT, ikke bare bredde ───────────────────────
     768×1024 (aspekt 0,75) og 1440×900 (1,60) er fundamentalt ulike
     komposisjoner. Delte de samme grenen, arvet portrett landskapets tall
     — B22 én gang til, på den andre aksen.
     ⚠️ Og `vmin` løser det ikke: på PORTRETT er vmin BREDDEN. `38vmin` er
     38 % av 768 på et lerret som er 1024 høyt, og kjernen blir en flekk i
     hjørnet i stedet for en lyskilde.
     Regelen som gjelder begge: gløden er forankret i BUNNHJØRNET, så det er
     hvor høyt den klatrer som avgjør om den leser som lys. Den VERTIKALE
     radien måles derfor mot høyden (`vh`) i begge grener. Den HORISONTALE
     måles mot det som faktisk begrenser den: høyden i landskap (der bredden
     er rikelig), bredden i portrett (der 105vh ville blitt 140 % av flaten
     og vasket den ut igjen). */
  /* LANDSKAP ≥641 px. `vh` == `vmin` her per definisjon (bredde ≥ høyde),
     så rendringen er identisk med den som er visuelt godkjent på 1440. */
@media(min-width:641px) and (min-aspect-ratio:1/1){
  .bakgrunn::before{
    background:
      radial-gradient(38vh 38vh at 81% 85%,
        rgba(230,125,55,.30) 0%, rgba(230,125,55,.10) 38%, rgba(230,125,55,0) 66%),
      radial-gradient(62vh 60vh at 88% 93%,
        rgba(150,81,44,.26) 0%, rgba(150,81,44,.09) 45%, rgba(150,81,44,0) 72%),
      radial-gradient(105vh 80vh at 78% 108%,
        rgba(71,44,31,.55) 0%, rgba(71,44,31,.18) 50%, rgba(71,44,31,.05) 78%,
        rgba(71,44,31,0) 100%)}
}
  /* PORTRETT ≥641 px (nettbrett). Vertikalt som landskap, horisontalt bundet
     av bredden. På 768×1024 gir det 338×348 / 553×573 / 906×778 px — samme
     absolutte tilstedeværelse som landskapsgrenen har på 1440×900
     (342×342 / 558×540 / 945×720), i stedet for `vmin`-grenens 292 px-flekk. */
@media(min-width:641px) and (max-aspect-ratio:1/1){
  .bakgrunn::before{
    background:
      radial-gradient(44vw 34vh at 81% 85%,
        rgba(230,125,55,.30) 0%, rgba(230,125,55,.10) 38%, rgba(230,125,55,0) 66%),
      radial-gradient(72vw 56vh at 88% 93%,
        rgba(150,81,44,.26) 0%, rgba(150,81,44,.09) 45%, rgba(150,81,44,0) 72%),
      radial-gradient(118vw 76vh at 78% 108%,
        rgba(71,44,31,.55) 0%, rgba(71,44,31,.18) 50%, rgba(71,44,31,.05) 78%,
        rgba(71,44,31,0) 100%)}
}
@keyframes bgPust{to{transform:translate3d(-2%,-1.5%,0) scale(1.055)}}
  /* ── GROVT TEKSTURLAG — «MER TEKSTUR, IKKE MER LYS» (Niklas) ───────────
     Målt rotårsak til hvorfor det ikke holdt å skru opp `.grain`:
     båndpass-energien deler seg i fin (1–2 px), midt (2–8 px) og grov
     (8–30 px). `.grain` ligger på `baseFrequency 0.85` = ren FIN struktur.
     Å heve den fra .026 til .046 målte 2,72 → 4,72 i fin-båndet mens
     MIDT-båndet sto stille på 0,61 → 0,69 — mer energi i båndet som
     allerede er mettet, null ny struktur, og middelet løftes. Det er
     nøyaktig «leser som støy».
     Strukturen kommer av et lag i ANNEN SKALA: `baseFrequency 0.09` over en
     480 px tile gir korrelasjonslengder i midt-båndet. Målt: midt 0,61 →
     3,11 ved opasitet .05, med luminans 12,7 → 13,2. 5× struktur ved
     praktisk talt uendret opplevd mørke.
     ⚠️ HELT STATISK: ingen `animation`, ingen `filter`. Det er derfor det er
     et eget lag og ikke lagt på `::before`, som bærer `filter:blur(22px)` —
     å blurre støyen ville utslettet den, og å animere den ville kostet
     AK19-budsjett vi ikke har.
     ⚠️ Samme kontrast-rigg som `.grain`: `saturate 0` fjerner fargestøyen,
     `slope 9 / intercept -4.44` gjør middel og amplitude til uavhengige
     spaker, `feFuncA slope 0 intercept 1` tvinger alfa til 1 så laget er
     bakgrunnsuavhengig.

     ⚠️ LAGREKKEFØLGE — GROVLAGET LIGGER BAK KARTET, KORNET FORAN.
     `.bakgrunn` er z-index −1, så BEGGE pseudo-elementene (glød + dette
     grovlaget) ligger BAK kartlerretet (z-index 0), mens `.grain`
     (z-index 1) ligger FORAN det. Det er tilsiktet: finkornet skal legge
     seg over punktskyen så den ikke leser digital, mens grovlaget er
     underlag — en struktur i flaten kartet ligger PÅ. At de to støylagene
     havner på hver sin side av kartet er altså et valg, ikke en glipp.
     Ikke «rett» det ved å flytte grovlaget opp.

     ⚠️ OPASITET OG BASE ER ETT PAR — ALDRI JUSTER OPASITETEN ALENE.
     Hvert støylag legger LYS til nesten-svart. Målt: ~0,84 LSB per 0,01
     opasitet. Skrur du opasiteten opp uten å senke `.bakgrunn`s base
     tilsvarende, blir flaten lysere — og «lysere» er nøyaktig det Niklas
     ikke ba om da han ba om mer tekstur.
     Kalibrerte par (målt på rendret PNG, mørkt felt 1440×900):
       opasitet   base       rendret luminans   midt-bånd
         .02      #080807         12,02            1,45   ← VALGT
         .03      #070706         12,51            2,10
         .04      #060605         12,43            2,63
         .05      #050504         12,48            3,26
     ⚠️ HVORFOR `.02` OG IKKE MER — Niklas, etter å ha sett alle trinnene:
     «På .03 leser grovlaget som FLEKKER — uregelmessige lyse klumper som
     konkurrerer med kartpunktene. På .05 er det enda tydeligere: et synlig
     skyet mønster som trekker blikket. Det leser som kompresjonsartefakter,
     ikke som papir.» Taket er altså ikke teknisk, men persepsjonelt: over
     ~1,5 i midt-båndet slutter strukturen å lese som materiale og begynner
     å lese som feil. Å skru opp igjen «fordi det knapt synes» er å
     gjenåpne nettopp det han avviste.
     ⚠️ HVORFOR PARITETEN FINNES — IKKE «RYDD OPP» I DEN:
     `.bakgrunn` har med vilje en ANNEN hex enn `--bg` (#0d0c0b). Det ser ut
     som en inkonsistens og er det motsatte. Footeren maler rå `var(--bg)`
     (`components.js`: `footer{background:var(--bg)}`), og overscroll-flaten
     gjør det samme — begge uavhengig av hva `.bakgrunn` gjør. Målet er at
     `.bakgrunn` MED støylagene skal RENDRE til samme luminans som de
     flatene (12,14 LSB). Treffer den ikke, får du en synlig horisontal søm
     ved footerkanten. Pariteten er invarianten; hex-likhet er det ikke. */
.bakgrunn::after{content:"";position:absolute;inset:0;pointer-events:none;
  /* `absolute`, ikke `fixed`: forelderen er allerede `position:fixed;inset:0`,
     så rendringen er identisk (bevist med 0 px diff), men vi unngår en andre
     fixed-positioning root inne i et element som bærer
     `view-transition-name` — det minst veldefinerte hjørnet av VT-spesen. */
  /* ⚠️ YTELSE — DET SOM MÅ MÅLES PÅ MOBIL ER FIRST PAINT, IKKE FPS.
     `feTurbulence` over en 480×480 tile rasteriseres på HOVEDTRÅDEN ved
     FØRSTE MALING. Laget er statisk, så det koster ingenting per frame og
     er usynlig i en fps-måling — kostnaden ligger helt i oppstarten.
     Niklas: «en side telefonselgere sender folk til har ikke råd til
     300 ms ekstra før første piksel.» AK19 er fortsatt åpen; når den
     lukkes, MÅ first paint på ekte mobil være en del av den. */
  opacity:.02;background-size:480px 480px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='480'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.09' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='9' intercept='-4.44'/%3E%3CfeFuncG type='linear' slope='9' intercept='-4.44'/%3E%3CfeFuncB type='linear' slope='9' intercept='-4.44'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='480' height='480' filter='url(%23g)'/%3E%3C/svg%3E")}
  /* ── A1 — KORNET VAR MATEMATISK UTSLETTET ─────────────────────────────
     ROTÅRSAK (målt, ikke antatt): `mix-blend-mode:overlay` er definert som
     `2·b·s` for base b < 0,5. Med basen #0b0a09 (b ≈ 0,04) spenner HELE
     utgangen [0, ~0,08], og ved 4,5 % opasitet ble maks avvik ±0,59 av ETT
     8-bit trinn — altså under kvantiseringsgrensen.
     Målt på rendret PNG, 1440×900: korn-amplitude 0,000 LSB i mørkt felt,
     og banding-løpene var BYTE-IDENTISKE med `.grain{display:none}`
     (kontrollprøve kjørt). Kornet var umulig å skille fra fravær.

     TRE ENDRINGER, hver med sin grunn:
     1. `mix-blend-mode:normal` — den ENESTE moden med bakgrunns-UAVHENGIG
        amplitude (målt identisk std på flat base, mid-gradient og
        glødkjerne). `overlay`/`soft-light` kollapser mot svart;
        `screen`/`plus-lighter` gir dårligere amplitude per middel-løft.
     2. Støyen får TVUNGET ALFA = 1 (`feFuncA` slope 0, intercept 1) og
        HEVET KONTRAST (`feFuncR/G/B` slope 9). Det gjør de to spakene
        UAVHENGIGE: middelet styrer middel-løftet, kontrasten styrer
        amplituden. `intercept = mu − slope/2` med mu = 0,06.
        `feColorMatrix saturate 0` fjerner fargestøyen først — uten den
        får kornet en farget flimring på nesten-svart.
     3. `opacity:.026` — kalibrert mot 1 og 2, ikke skrudd på i blinde.
     Målt resultat: amplitude 2,78 LSB UNIFORMT (samme i mørkt felt som i
     glødkjernen), banding maks konstant løp 525 px → 7 px.
     ⚠️ Skru ALDRI kun på `opacity` her igjen uten å måle: det var nettopp
     den spaken som ikke virket, fordi feilen lå i blandingen
     (LÆRDOM [NÅR EN VERDI «IKKE VIRKER», MISTENK BLANDINGEN]). */
.grain{position:fixed;inset:-50%;width:200%;height:200%;z-index:1;pointer-events:none;
  opacity:.026;mix-blend-mode:normal;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='9' intercept='-4.44'/%3E%3CfeFuncG type='linear' slope='9' intercept='-4.44'/%3E%3CfeFuncB type='linear' slope='9' intercept='-4.44'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  animation:bgKorn 1.2s steps(4) infinite}
@keyframes bgKorn{0%{transform:translate(0,0)}25%{transform:translate(-2%,1%)}
  50%{transform:translate(1%,-2%)}75%{transform:translate(-1%,-1%)}100%{transform:translate(0,0)}}
  /* AK5 — «faktisk stillstand», ikke «raskere animasjon». animation-name
     skal måles til `none`, ikke til en kort varighet. */
@media(prefers-reduced-motion:reduce){
  .bakgrunn::before{animation:none}
  .grain{animation:none}
}
