﻿/* ─────────────────────────────────────────────────────────────
   Brand typeface — Satoshi (Småkraftforeninga)
   ───────────────────────────────────────────────────────────── */
@font-face {
  font-family: 'Satoshi';
  src: url('assets/fonts/Satoshi-Light.ttf') format('truetype');
  font-weight: 300; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Satoshi';
  src: url('assets/fonts/Satoshi-Regular.ttf') format('truetype');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Satoshi';
  src: url('assets/fonts/Satoshi-Medium.ttf') format('truetype');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Satoshi';
  src: url('assets/fonts/Satoshi-Bold.ttf') format('truetype');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Satoshi';
  src: url('assets/fonts/Satoshi-Black.ttf') format('truetype');
  font-weight: 900; font-style: normal; font-display: swap;
}

/* ─────────────────────────────────────────────────────────────
   Reset & base
   ───────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scrollbar-gutter: stable; }

:root {
  /* ── Småkraft brand palette (from Fargepalett.png) ──
     Primary trio:  blue #0050A1 · orange #F29000 · green #0DA100
     Soft tints:    blue #CCDCEC · peach #FCE9CC · green #DDECCC · grey #F2F2F2 */
  --brand-blue:        #0050a1;   /* R0  G80  B161 — primary */
  --brand-blue-deep:   #003c79;   /* shade for nav / headers   */
  --brand-blue-900:    #002a55;   /* deepest — map canvas      */
  --brand-blue-light:  #2f7dd1;   /* tint for links / accents  */
  --brand-orange:      #f29000;   /* R242 G144 B0 — secondary  */
  --brand-orange-deep: #cf7c00;
  --brand-green:       #0da100;   /* R13 G161 B0 — tertiary    */
  --brand-green-deep:  #0a8400;
  /* Ends of a green ramp, mirroring --brand-blue-900/-light: the two exist
     for Leverandorindustrien's activity bars, which need three separable
     shades of one hue the way the blue bars did. */
  --brand-green-900:   #054d00;   /* deepest — darkest bar     */
  --brand-green-light: #7fd070;   /* tint for the lightest bar */

  --tint-blue:   #ccdcec;         /* R204 G220 B236 */
  --tint-blue-hover: #b4c0cc;     /* hover state — darker, cooler, greyer */
  --tint-peach:  #fce9cc;         /* R252 G233 B204 */
  --tint-peach-hover: #f5dab3;    /* hover state — same role as --tint-blue-hover */
  --tint-green:  #ddeccc;         /* R221 G236 B204 */
  --tint-green-hover: #cbe0b5;    /* hover state — same role as --tint-blue-hover */
  --tint-grey:   #f2f2f2;         /* R242 G242 B242 */

  /* ── Legacy aliases (kept so existing rules resolve) ── */
  --green-900: var(--brand-blue-900);
  --green-800: var(--brand-blue);
  --green-600: var(--brand-blue-light);
  --green-400: var(--brand-orange);
  --green-200: var(--tint-blue);

  --off-white:  #ffffff;
  --border:     #d8d8d8;
  --text:       #15263b;
  --text-muted: #7c8a9a;

  /* ── The hover-hint plate ─────────────────────────────────────
     One look for every hover hint on the dashboard, chart or not. The values
     are the SAME ones Chart.defaults.plugins.tooltip is set to in script.js
     (search "One tooltip look for every chart"): a white plate, a hair border
     and dark text, the object the surrounding cards are made of. Canvas cannot
     read a custom property, which is why the chart half is written in JS; these
     are here so the DOM half cannot drift from it. Change both together.
     Anything new that needs a hint should reach for data-tip (see the
     [data-tip]::after rule) rather than inventing a fourth box. */
  --tip-bg:     #ffffff;
  --tip-border: #d0d8e4;
  --tip-ink:    #444444;
  --tip-radius: 6px;
  --tip-pad:    10px;

  /* 44px, down from 56 in two steps. The bar holds a 28px mark and a ~29px
     chip now, so what has gone was clearance rather than content, and every
     pixel of it comes back to the sheet below through --chrome-h. */
  --nav-h:      44px;
  --tab-h:      44px;            /* legacy — tabs now live inside the nav */
  /* No --footer-h, and the footer is not in --chrome-h: it is not a band at
     the foot of the window at all but the last line inside whatever scrolls
     (see .site-footer), so it takes no strip of the sheet and has no height
     anything else needs to know. */
  --chrome-h:   var(--nav-h);

  /* ── Page canvas cap ──────────────────────────────────────────
     Design size of the dashboard: 1920 x 1024, which was a maximized 1080p
     window (1080 - 56px nav) at 100% browser zoom when it was drawn. The nav
     is 50px now, so that window fits the canvas at a hair over 1, which is
     the auto-fit doing its job, not a size to re-cut the design to: the
     canvas is a fixed composition and applyPageZoom scales it to whatever is
     left over. The interior is already pinned at this size (every clamp(px, Nvh, px) inside the panels hits its px
     ceiling around a 1080p viewport height. Above the design size (zooming
     out grows the CSS viewport, as does a larger monitor) the container used
     to keep stretching while its contents did not, which spread the cards
     and charts apart. These caps freeze the content box instead and let the
     grey canvas show around it. Tune here, nowhere else. */
  --page-max-w: 1920px;
  --page-max-h: 1024px;
  /* The sheet's own height, and 1% of it. Viewport units resolve against the
     real window even inside the auto-fit zoom (measured — 100vh does NOT get
     divided by it), so every clamp(px, Nvh, px) in here would shrink twice on
     a laptop: once with the zoom, once again for the shorter viewport, leaving
     squat charts and tight padding that no longer matched the design at 100%.
     --sheet-vh is the drop-in replacement for 1vh inside the sheet: it
     resolves against the canvas, so the interior composition is identical at
     every window size and only the scale changes. */
  --sheet-h:  min(var(--page-h, calc(100vh - var(--chrome-h))), var(--page-max-h));
  --sheet-vh: calc(var(--sheet-h) / 100);

  /* ── KPI card type scale: three sizes, and only three ──────────────────────
     Every card on every slide reads at one of these. It used to be eight sizes
     within a single screen (9 / 20 / 27 / 29 / 36 / 39 / 40 / 60), with several
     pairs close enough to look like slips rather than steps, and the supporting
     line set LARGER than the heading it supported.
       --fs-kpi-value  the figure. Tracks sheet height so a short window shrinks
                       it rather than pushing the card off the page.
       --fs-kpi-meta   everything under or beside the figure — the "Oppdatert
                       11:45" timestamp, the "Tilsvarer … dagsverk" equivalents,
                       the "Nok til å dekke …" line. One size for all of them.
       --fs-kpi-title  the card's own name.
     Redefined at the phone breakpoint, so a size is changed in one place. */
  --fs-kpi-value: clamp(22px, calc(4.5 * var(--sheet-vh)), 40px);
  --fs-kpi-meta: 22px;
  --fs-kpi-title: 27px;
  /* Left inset for a KPI card's content column. Wide enough to clear the (i)
     dot in the card's top-left corner: left-aligned, the heading's first letter
     ran underneath it. Heading, figure and context line all start here, so the
     card reads down one edge. */
  --kpi-inset: 34px;

  font-family: 'Satoshi', system-ui, sans-serif;
  font-size: 14px;
  color: var(--text);
  background: var(--tint-grey);   /* soft grey page canvas (not pure white) */
  -webkit-font-smoothing: antialiased;
}

/* ─────────────────────────────────────────────────────────────
   Nav
   ───────────────────────────────────────────────────────────── */
.nav {
  height: var(--nav-h);
  background: #fff;
  display: flex;
  align-items: center;
  gap: 12px;
  padding-block: 0;
  /* Bar and sheet both run full-bleed now, so --sheet-w is the window width and
     this resolves to the 20px floor. The expression stays because it is what
     KEEPS them aligned: --sheet-w is the sheet's real on-screen width after the
     auto-fit zoom (set by applyPageZoom) and the nav is never scaled, so if the
     sheet ever centres itself again the brand follows it instead of being
     stranded far to its left. */
  padding-inline: max(20px, calc((100% - var(--sheet-w, var(--page-max-w))) / 2));
  position: sticky;
  top: 0;
  z-index: 200;
  box-shadow: 0 1px 0 var(--border), 0 1px 8px rgba(0,0,0,0.06);
}

/* Brand lockup: round mark on a white chip + wordmark */
.nav-brand {
  display: flex;
  align-items: center;
  gap: 11px;
  flex-shrink: 0;
  text-decoration: none;
  user-select: none;
}

.nav-brand__mark {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  object-fit: contain;
}

.nav-brand__text {
  font-family: 'Satoshi', sans-serif;
  font-weight: 800;
  font-size: 15px;
  color: var(--text);
  letter-spacing: 0.01em;
  line-height: 1;
}

.nav-brand__text em {
  font-style: normal;
  font-weight: 500;
  color: var(--brand-orange);
}

/* Mark only, no wordmark, once the tab strip gets close to it.
   The strip is a fixed 1065px of chips centred in the true viewport middle (see
   .tab-bar), so the clearance either side shrinks with the window while the
   strip does not: measured 72px of gap at 1920, 34px at 1560, 10px at 1512, and
   an overlap by 1440 — i.e. it collides at ordinary laptop widths. Dropping the
   wordmark pulls the lockup's right edge from 206px in to 54px, which buys back
   152px and keeps the mark clear well past where the strip starts scrolling.
   The nav is deliberately NOT scaled by the auto-fit zoom (applyPageZoom only
   scales the sheet, so nav labels stay legible), which is why this is a plain
   width breakpoint rather than something derived from the zoom. */
@media (max-width: 1600px) {
  .nav-brand__text { display: none; }
}

/* legacy text logo (kept for fallback) */
.nav-logo {
  font-family: 'Satoshi', sans-serif;
  font-weight: 800;
  font-size: 16px;
  color: #fff;
  letter-spacing: 0.01em;
  user-select: none;
}

.nav-logo em {
  font-style: normal;
  color: var(--brand-orange);
}

/* ─────────────────────────────────────────────────────────────
   Footer
   ───────────────────────────────────────────────────────────── */
/* One quiet line at the end of the scrolling column, and deliberately little
   else: what it is for is the edge itself. The column used to stop wherever
   its last card happened to stop, which reads as content cut off rather than
   content that has ended. Whatever belongs down here later (contact, kilder,
   sist oppdatert) goes inside it without moving anything.

   It lives INSIDE the thing that scrolls (the column beside the map on the
   desktop, the document itself on a phone), which placeFooter (script.js)
   re-parents it into as the slide or the layout changes. That is the whole
   mechanism: last in the flow of the scroller means you reach it exactly when
   there is nothing left to scroll, with no state to track, nothing floating
   over the cards you were reading, and nothing across the map, which does not
   scroll and has no end to announce.

   Outside .main in the markup, so it is the auto-fit zoom's business only
   while it sits inside a column (which is scaled) rather than in the body
   (which is not). Either way it is the same size as its surroundings. */
.site-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 14px;
  padding: 12px 0 calc(12px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border);
  color: var(--text-muted);
  /* The columns are grey and the phone's document is grey; a white band would
     be a second sheet edge under the real one. */
  background: transparent;
}

.site-footer__mark {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  object-fit: contain;
  opacity: 0.7;
}

.site-footer__text {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.01em;
}

/* The nedbør overlay's attribution, which the colourbar used to carry inside
   itself. Only present while that overlay is on, so it is separated by a rule
   rather than by a bullet: the footer has a fixed pair of items and this is a
   third that comes and goes, and a dangling separator is worse than none. */
.site-footer__src {
  font-size: 12px;
  padding-left: 8px;
  border-left: 1px solid var(--border);
}
.site-footer__src[hidden] { display: none; }

/* ─────────────────────────────────────────────────────────────
   Tab bar
   ───────────────────────────────────────────────────────────── */
.tab-bar {
  /* Absolutely centred in the nav so the tabs sit in the true viewport
     middle, independent of the (left-aligned) brand lockup width. */
  position: absolute;
  left: 50%;
  top: 0;
  transform: translateX(-50%);
  height: 100%;
  /* Leaves room for the brand mark at either side. 48px was only the nav's own
     padding, so between roughly 860px and 1150px the centred strip still ran
     across the mark even with the wordmark hidden (measured: 30px of overlap at
     1100). 140px keeps its left edge past the mark at every width where this
     strip is used; it scrolls horizontally inside the narrower box, which it
     already did. */
  max-width: calc(100% - 140px);
  display: flex;
  justify-content: center;     /* centre the tabs in the bar */
  align-items: center;         /* chips sit centred, not full-height */
  gap: 4px;
  overflow-x: auto;
  scrollbar-width: none;       /* Firefox */
}

.tab-bar::-webkit-scrollbar { display: none; }

.tab {
  flex-shrink: 0;
  padding: 6px 13px;
  border: none;
  border-radius: 999px;
  background: transparent;
  font-family: inherit;
  /* Three quarters of the 0.86 these chips were set at a moment ago, so ~14px
     against the 22px the sentences inside the sheet read at. Still written as
     a fraction of the token rather than as a literal: it is redefined at the
     phone breakpoint, and a hard 14px would stop following it.
     These chips do name the sections of the dashboard, which is why they were
     on the title scale to begin with. What settled it the other way is that
     they are chrome: the bar is read once, on the way in, and then sits above
     everything for the rest of the session, where a title-sized label competes
     with the figures it is supposed to be a frame around.
     The strip already scrolls horizontally inside max-width: calc(100% - 140px)
     and the bar is 44px tall against ~27px of chip, so the label costs no
     layout on either axis at any width where the strip is used. */
  font-size: calc(var(--fs-kpi-meta) * 0.645);
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.15s, background-color 0.15s;
}

.tab:hover     { color: var(--brand-blue); background: var(--tint-blue); }
.tab.is-active { color: var(--brand-blue-deep); background: var(--tint-blue); }

/* ── Phone group switcher ─────────────────────────────────────
   Three circles standing in for the three tab groups. Off by default: above
   640px the full strip fits, so there is nothing to collapse. The open/close
   behaviour and the overlay live in the max-width:640px block near the bottom
   of this file, next to the rest of the phone nav. */
.tab-groups { display: none; }

/* ── Tab groups ───────────────────────────────────────────────
   Three thematic families, each with a subtle tinted band behind
   the group and its own accent colour when active:
     .tab--prod   blue   — produksjon / installert effekt
     .tab--ring   orange — samfunnseffekter / sysselsetting
     .tab--verdi  green  — inntekter / verdier
   The band is a low-alpha wash so it reads as grouping against the
   deep-blue nav without competing with the active chip. Adjacent
   tabs in a group butt together; only the group's outer edges are
   rounded, so each family reads as one continuous block. */
.tab-bar { gap: 0; }

.tab--prod, .tab--ring, .tab--verdi {
  border-radius: 0;
  margin: 0;
}

/* Round only the outer edges of each group. Uses sibling adjacency so the
   rules hold if tabs are added to or moved between groups. */
.tab--prod:first-child,
:not(.tab--prod) + .tab--prod   { border-radius: 999px 0 0 999px; }
.tab--ring:first-child,
:not(.tab--ring) + .tab--ring   { border-radius: 999px 0 0 999px; }
.tab--verdi:first-child,
:not(.tab--verdi) + .tab--verdi { border-radius: 999px 0 0 999px; }

.tab--prod:last-child,
.tab--prod:not(:has(+ .tab--prod))   { border-radius: 0 999px 999px 0; }
.tab--ring:not(:has(+ .tab--ring))   { border-radius: 0 999px 999px 0; }
.tab--verdi:not(:has(+ .tab--verdi)) { border-radius: 0 999px 999px 0; }

/* A single-chip group needs both ends rounded */
.tab--ring:not(:has(+ .tab--ring)):is(:first-child, :not(.tab--ring) + .tab--ring),
.tab--prod:not(:has(+ .tab--prod)):is(:first-child, :not(.tab--prod) + .tab--prod),
.tab--verdi:not(:has(+ .tab--verdi)):is(:first-child, :not(.tab--verdi) + .tab--verdi) {
  border-radius: 999px;
}

/* Group bands, white at rest. They used to be their group's brand hue at
   0.15 alpha, so every chip in the strip carried a wash whether or not it was
   the one you were on, and the hover had to deepen an already-tinted band to
   register. White gives the hover somewhere to go, and matches the KPI cards,
   which took the same treatment for the same reason: the tint is a STATE here,
   not the resting surface.
   What the bands were doing is now carried by the 8px gap between families and
   by the outer-edge rounding, both a few rules up. */
.tab--prod,
.tab--ring,
.tab--verdi { background: #fff; }

/* Small gap where one family ends and the next begins */
:not(.tab--prod)  + .tab--prod,
:not(.tab--ring)  + .tab--ring,
:not(.tab--verdi) + .tab--verdi { margin-left: 8px; }

/* Hover: the group's own tint, now against a white band rather than a tinted
   one. The same three --tint-* tokens the KPI cards hover to, so a colour
   means the same thing in the nav as it does on a card, instead of the strip
   keeping a private rgba scale of its own.
   Text goes to the group's deep shade rather than white: white read fine
   against the solid is-active fill but not against a light tint. */
.tab--prod:hover:not(.is-active)  { color: var(--brand-blue-deep);   background: var(--tint-blue); }
.tab--ring:hover:not(.is-active)  { color: var(--brand-orange-deep); background: var(--tint-peach); }
.tab--verdi:hover:not(.is-active) { color: var(--brand-green-deep);  background: var(--tint-green); }

/* Active: the same lighter-fill/darker-text pairing as the hover state just
   above, so the tab you're on reads as a settled version of the hover rather
   than a heavier, differently-coloured control. */
.tab--prod.is-active  { color: var(--brand-blue-deep);   background: var(--tint-blue); }
.tab--ring.is-active  { color: var(--brand-orange-deep); background: var(--tint-peach); }
.tab--verdi.is-active { color: var(--brand-green-deep);  background: var(--tint-green); }

/* ─────────────────────────────────────────────────────────────
   Slides
   ───────────────────────────────────────────────────────────── */
/* Full-bleed: the sheet takes the whole window width at every size. It used to
   be capped at the design width and centred (see the note inside). */
.main {
  width: 100%;
  /* No width cap, deliberately. --page-max-w still exists and the nav still
     falls back to it, but capping the SHEET at it is what put grey down both
     sides of the dashboard on any normal laptop.
     The reason: applyPageZoom picks one uniform scale, min(w/1920, h/1024),
     and on a wide-but-short window height wins. The sheet is then scaled by
     less than the width alone would allow, so `width: 100%` resolves to more
     than 1920 logical px, max-width clamped it back to 1920, and
     margin-inline: auto centred the shortfall as two grey bands. At 1857x915
     that was 1633px of dashboard in 1857px of window, 112px of grey each side.
     Uncapped, the sheet lays out at whatever 100% resolves to (2182px in that
     example) and renders edge to edge. The layout is fr-based and takes the
     extra width as wider panels; 1920 stays the design size, not a ceiling.
     Height still sets the scale, so nothing gets vertically tighter than it
     already was. */
  max-width: none;
  margin-inline: auto;
  /* Auto-fit: applyPageZoom (script.js) scales the whole sheet down so the
     1920x1024 canvas fits the window — a laptop viewport shows the entire
     slide instead of clipping its bottom row. 1 (no scaling) whenever the
     window is at least the design size, or on the stacked mobile layouts. */
  zoom: var(--page-zoom, 1);
}

.slide          { display: none; }
/* Every slide has one shape: a full-bleed title header, then the panel-beside-
   map row filling everything under it. The header used to live inside the left
   panel of .split, so it ended at the 3fr/2fr seam and the map ran up alongside
   it — a title that looked cut off halfway across the sheet. Hoisted out (see
   index.html: it is now a direct child of the <section>), it runs the full
   width and the map starts below it.
   Nothing goes UNDER the row. Oppsummering's daily-production chart and
   Konsesjon's case table both spent a spell down there in a full-width band,
   which meant bounding the row to make space — and then reading either one
   meant scrolling the map and the header's region controls off the screen,
   and those are what you pick the chart's region WITH. Both now live inside
   the left panel, which scrolls under a map that stays put. */
.slide.is-active { display: flex; flex-direction: column; }

/* The sheet itself: exactly one windowful. The height and the sheet-edge
   shadow used to sit on .split, which is no longer the whole sheet — the
   header above it is part of it too. Background for the same reason: the
   canvas behind is this grey, so this is what marks where the sheet starts. */
.slide {
  height: var(--sheet-h);
  background: var(--tint-grey);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07);
}
/* .split takes what the header leaves rather than a windowful of its own, so
   the two together still come to exactly one sheet. min-height: 0 so the
   panels inside can actually resolve against it (and to beat the 520px floor
   below, which was written for a .split that was the whole sheet). */
.slide > .split {
  height: auto;
  flex: 1;
  min-height: 0;
  box-shadow: none;
}

/* Generic inner for placeholder slides */
.slide-inner {
  max-width: 720px;
  margin: 64px auto;
  padding: 0 24px;
}

.slide-heading {
  font-family: 'Satoshi', sans-serif;
  font-size: 28px;
  color: var(--green-800);
  margin-bottom: 12px;
}

.slide-body {
  color: var(--text-muted);
  line-height: 1.7;
}

/* ── Scroll cue ────────────────────────────────────────────────────
   The panels beside the maps scroll, and nothing said so: the content simply
   ran off a clean edge, which reads as the end of the page rather than the top
   of it. A sticky bar at the foot of the scroller — a fade deep enough that the
   cut-off row is visibly cut off, and a button that carries on for anyone who
   would rather press than drag.

   Deliberately loud. The first version was a white pill on a white card and was
   effectively invisible until you hovered it, which defeats the point: someone
   who does not notice it never finds the table or the charts underneath. It is
   now a solid button in the slide's own accent colour, and it NAMES what is
   below (data-scroll-hint) rather than saying "more" — "Saker og
   saksbehandlingstid" is a reason to scroll, "Mer under" is not.

   position:sticky inside the scroller rather than absolute on the panel: the
   two scrolling columns have different shapes (Oppsummering's IS the panel,
   Konsesjon's is a child of it), and sticky works for both without either
   needing a positioned ancestor. */
.scroll-cue {
  position: sticky;
  bottom: 0;
  left: 0;
  z-index: 20;
  /* Tall enough to stand the button and its stack in, no taller: this used to
     carry a white gradient up the last 78px so a cut-off row read as cut off,
     and the fade was doing more harm than the ambiguity it fixed — it dimmed
     the bottom of every figure in the column and read as a smudge on the card.
     The button alone says there is more below. */
  height: 54px;
  margin-top: -54px;               /* overlays the content, costs no layout */
  flex-shrink: 0;                  /* the scrollers are flex columns */
  pointer-events: none;            /* only the button inside is clickable */
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: 10px;
  /* One accent per thematic group, same as the tabs and the pills. Held as a
     variable because the button and the stack plates behind it both need it.
     A light tint rather than the solid brand colour, with --cue-ink carrying
     the matching deep shade for the label and chevron: the same lighter-fill/
     darker-text pairing as the toggles and the region panel, so a selected
     control doesn't read heavier than everything around it. */
  --cue-accent: var(--tint-blue);
  --cue-ink: var(--brand-blue-deep);
}
.slide[data-group="ring"]  .scroll-cue { --cue-accent: var(--tint-peach); --cue-ink: var(--brand-orange-deep); }
.slide[data-group="verdi"] .scroll-cue { --cue-accent: var(--tint-green); --cue-ink: var(--brand-green-deep); }

/* The button and the plates share one positioning context, so the plates can
   peek out from behind it without either of them being a pseudo-element on the
   button: a negative z-index there would paint over the button's own fill. */
.scroll-cue__stack {
  position: relative;
  display: inline-flex;
  pointer-events: none;
}
.scroll-cue__plate {
  position: absolute;
  border-radius: 22px;
  background: var(--cue-accent);
  opacity: 0;
  transition: opacity 0.18s ease;
}
/* Peeking below and inset at the sides, like the next card in a deck. Darker
   the further back it sits, which is what makes it read as depth rather than as
   a misaligned duplicate. */
.scroll-cue__plate--1 {
  inset: 7px 5px -5px;
  filter: brightness(0.86);
}
.scroll-cue__plate--2 {
  inset: 12px 11px -10px;
  filter: brightness(0.74);
}
/* Shown only when there is something after this stop — one plate for one more,
   two for two or more. See data-more in syncScrollCues. */
.scroll-cue[data-more="1"] .scroll-cue__plate--1,
.scroll-cue[data-more="2"] .scroll-cue__plate--1,
.scroll-cue[data-more="2"] .scroll-cue__plate--2 { opacity: 1; }
/* .panel--dark > * and .kons-body's own children rules set position:relative on
   every direct child (so the watermark sits behind them). Same specificity as
   .scroll-cue and later in this file, so they silently won and the cue was laid
   out inline BELOW the panel — created, correct, and permanently out of view.
   Said again, more specifically, where the conflict actually is. */
.panel--dark > .scroll-cue,
.aaret-body > .scroll-cue,
.kp-body-wrap > .scroll-cue,
.utbygging-body > .scroll-cue {
  position: sticky;
  z-index: 20;
}

/* Nothing left to invite: the cue is REMOVED rather than faded now (see
   syncScrollCues), because "no next stop" and "scrolled to the bottom" are the
   same state and one of them is easier to ask about. */

/* Gone entirely wherever the layout stacks. There the slide is as tall as its
   content and the PAGE scrolls (see .slide { height: auto } in the 860 block),
   so the column is not a scroller and the cue was a sticky button over content
   that plainly continues below it — inviting a press to do what the next
   thumb-flick does anyway. Both stacked layouts, so a short landscape window
   wider than 860px is covered too. Suppressed here rather than skipped in
   syncScrollCues, which runs before _narrowMQ exists.
   `and (not (pointer: fine))` on every stacked/condensed breakpoint in this
   file (860, 640, and the 560-landscape pair): a mouse or trackpad's presence
   means a real pointing device is attached, which page zoom never changes,
   where max-width/max-height do — the browser reports a narrower CSS
   viewport at every zoom step in, exactly the way it is meant to for
   --page-zoom (see applyPageZoom's own note on this). Without the guard, a
   desktop reader zooming in far enough fell through the same breakpoint a
   phone uses and got the phone's stacked, single-column layout instead of a
   bigger version of the one they were already looking at. A touchscreen
   still reports (pointer: coarse) regardless of zoom, so it keeps stacking
   exactly as before. Mirrored in script.js on _stackedMQ, _condenseMQ,
   _phoneMQ and _narrowMQ — all four have to agree with these rules, or JS
   and CSS disagree about which layout is showing. */
@media (max-width: 860px) and (not (pointer: fine)),
       (max-height: 560px) and (orientation: landscape) and (not (pointer: fine)) {
  .scroll-cue { display: none; }
}

.scroll-cue__btn {
  pointer-events: auto;
  position: relative;              /* over its own stack plates */
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 18px;
  border: none;
  border-radius: 22px;
  background: var(--cue-accent);
  color: var(--cue-ink);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.14);
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition: transform 0.15s, box-shadow 0.15s, filter 0.15s;
}
.scroll-cue__btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 7px 20px rgba(0, 0, 0, 0.28);
  filter: brightness(1.06);
}
.scroll-cue__btn svg {
  width: 16px;
  height: 16px;
  /* A slow nudge — enough to catch the eye at the edge of vision, not enough to
     compete with the figures above it. */
  animation: scroll-cue-nudge 1.8s ease-in-out infinite;
}
@keyframes scroll-cue-nudge {
  0%, 55%, 100% { transform: translateY(0); }
  28%           { transform: translateY(3.5px); }
}
@media (prefers-reduced-motion: reduce) {
  .scroll-cue__btn svg { animation: none; }
}

/* ── Saksbehandlingstid ────────────────────────────────────────────
   In the scrolling column above the case table, so it costs the chart and the
   map nothing. The title is painted into the plot's own strip by
   konsBehBoxPlugin, the legend sits inside the card below the canvas, and the
   info dot and export menu take the two top corners.
   The card used to BE .chart-wrap (one card, not a card inside a card) — true
   back when this had nothing beside the canvas. Now it has the same pie the
   other two chart cards do (.kons-stolper-row), and that pie has to sit inside
   the SAME white box as the histogram, or the two chart cards read as built
   differently: one box with a pie and a chart in it, one bare aside floating
   next to a separately-boxed chart. So the white box moves out here, same
   background/border/radius/padding as .kons-view, and .kons-beh__chart below
   gives up its own — the same trade .kons-chart__plot already makes for
   stolper and aarlinje's canvases, which also sit inside a card that isn't
   themselves. */
.kons-beh {
  flex-shrink: 0;
  position: relative;              /* the info dot anchors here */
  margin-top: 14px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  /* No card chrome. It used to carry the same white background, border and
     radius .kons-view does, which made this a SECOND box stacked under the
     view card rather than another reading inside the same column: two framed
     rectangles where Konsesjonsstatus is one. The padding stays, since the
     info dot anchors to this box and the chart needs the inset. */
  padding: 18px 20px 14px;
}
/* Definite height: Chart.js needs one to draw into, and the column scrolls
   rather than handing out a share of itself. The extra 50 is the title strip
   the plugin paints above the plot. max-height is lifted off the inherited
   .chart-wrap rule for the same reason. background/border/padding are lifted
   too — #kons-beh is the box now, see the note there.
   Selector is .chart-wrap.kons-beh__chart, not .kons-beh__chart alone: with
   equal specificity the cascade falls to source order, and .chart-wrap (below,
   later in the file) was winning back its own white background/border/shadow
   on the same element — a second grey-bordered box drawn inside #kons-beh's.
   The extra class ties the win to specificity instead of position. */
.chart-wrap.kons-beh__chart {
  /* Twice the height it started at. The column scrolls, so the room is there,
     and a distribution with a 40-case peak and a dozen bins needs the vertical
     range to separate the small bars from the empty ones. */
  height: 600px;
  max-height: none;
  /* flex:1, not 0 0 auto: this is the row's WIDTH axis (.kons-stolper-row is
     flex-direction:row), and 0 0 auto sizes a flex item to its own content —
     with nothing forcing the canvas wider than that, the plot drew as a thin
     column hugging its own tick labels instead of filling the space beside
     the pie. height above still pins the cross-axis size regardless of flex,
     so growing on the main axis does not touch it. */
  flex: 1;
  display: flex;
  flex-direction: column;
  background: transparent;
  border: 0;
  padding: 0;
}

/* ── Digitalt kartlagt potensial ───────────────────────────────────
   A pool, not a stage — see the note in build_konsesjonsstatus_summary.py. It
   sits BESIDE the flow rather than above it: stacked, it ate the height the
   drawing needs, and the comparison it makes ("this is what is out there, that
   is what has been asked for") is a side-by-side one anyway. */
#kons-view-sankey {
  /* .kons-view is a centred COLUMN; this one is a row, so the aside sits beside
     the drawing rather than above it. Direction has to be stated explicitly or
     flex:0 0 132px below sizes the aside's height instead of its width. */
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: 18px;
}
/* .kons-view is flex:1 with min-height:0, which was right when the flow was the
   only thing in the column: it shrank into whatever the panel had left. With the
   case table AND the saksbehandlingstid chart below it as fixed-height siblings,
   that same rule squeezed the flow to a 40px sliver. A floor here makes the
   column overflow instead — which it is built to do (.kons-body scrolls, and
   fitScrollColumns zooms the whole column back onto one page). */
#kons-view-sankey { min-height: 510px; }

/* A region with no cases at all. The card is the same box, so the message is
   centred in it rather than sitting where the first bar would be — a sentence
   pinned to the top-left of an empty 400px card reads as a rendering failure,
   and the 510px floor above turns it into half a screen of white. */
#kons-view-sankey.kons-view--empty {
  min-height: 0;
  /* flex:1 on .kons-view made the message stretch to fill whatever the column
     had spare, which is the same half-screen of white the 510px floor gave. */
  flex: 0 0 auto;
}
#kons-view-sankey > .kons-sankey--empty {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 220px;
  zoom: 1;                    /* nothing to fit; never inherit a stale scale */
}
.kons-empty {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  max-width: 34ch;
  margin: 0;
  text-align: center;
}
.kons-empty__title {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--ink, #15263b);
}
.kons-empty__sub {
  font-size: 0.86rem;
  color: var(--ink-muted, #6b7a8d);
  line-height: 1.45;
}

#kons-view-sankey > .kons-sankey {
  flex: 1;
  min-width: 0;                 /* or the drawing's natural width sets a floor */
  position: relative;           /* the menu and the info dot anchor to this */
}
.kons-potensial {
  /* 200px, up from 132. The key's names are set at the KPI cards' own label size
     now (see .kons-pie-key__name), and at 18px a 132px column put "Resterende
     kartlagt potensial" on three lines. 200px holds it on two, which is what it
     took at the old size. The width comes out of the plot beside it, which is
     the trade: the key is read word by word and the plot is read as a shape. */
  flex: 0 0 200px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 18px;
  padding-right: 16px;
  border-right: 1px solid var(--border);
}
.kons-potensial__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  cursor: pointer;
}

/* ── Kartlagt potensial vs søkt om: one pie ─────────────────────────────────
   Was a pair of area-scaled circles; see konsPotensialHTML for why the two are
   parts of one whole and therefore belong in one figure. The applied-for wedge
   is pulled clear of the rest in the path itself, not here, so the geometry and
   the label agree at any size. overflow:visible is what lets it hang outside
   the ring without the box clipping it. */
.kons-pie {
  width: 100%;
  max-width: 116px;
  align-self: center;
  overflow: visible;
  flex-shrink: 0;
}
.kons-pie__slice { cursor: pointer; }
/* A hairline in the card's own white, so the pulled wedge reads as lifted off
   the ring rather than as a gap in it. */
.kons-pie__slice { stroke: #fff; stroke-width: 1.2; }

.kons-pie-key {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.kons-pie-key__row {
  display: grid;
  gap: 1px;
  cursor: pointer;
  line-height: 1.25;
}
.kons-pie-key__head {
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.kons-pie-key__dot {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  flex-shrink: 0;
  /* baseline-aligned text beside it, so the square needs nudging onto the
     x-height rather than sitting on the baseline. */
  transform: translateY(-1px);
}
/* The three sizes here are the KPI cards' own, so a wedge's name reads at the
   same weight as a card's name does on the same slide: 18px is what
   #slide-konsesjons .kpi-grid--select .kpi-card__label is set to. The figure
   above it takes --fs-kpi-meta, because a value must not be smaller than the
   label it answers, and the sub line drops to the card's own sub size. */
.kons-pie-key__name {
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
}
.kons-pie-key__val {
  font-size: var(--fs-kpi-meta);
  font-weight: 700;
  color: var(--text);
  padding-left: 14px;            /* clears the dot, so the figures line up */
}
.kons-pie-key__sub {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-muted);
  padding-left: 14px;
}

/* Beside the export menu, not under the drawing: the three lines of caption
   that used to sit there cost the flow the height they occupied. */
/* Doubled class on purpose. .info-dot sets `left: 8px`, it is declared LATER in
   this file, and a single class each means the two tie on specificity — so the
   base rule won and every "right-hand" dot in the app was drawn on the left,
   `right` set and quietly ignored. Naming .info-dot here outranks it outright,
   which order cannot undo. Same trick on the two rules below. */
.info-dot.kons-flow__info {
  position: absolute;
  top: 12px;
  left: auto;
  right: 54px;
  z-index: 3;
}
/* On the phone flow the export menu already owns the top-RIGHT corner, and the
   dot's usual 54px offset from it put the dot on the first circle. It goes to
   the top-left instead: the first row of circles is centred, so that corner is
   the one piece of this box nothing is ever drawn in (and .kons-circles'
   padding-top is the strip they share). */
.kons-circles > .info-dot.kons-flow__info {
  top: 2px;
  right: auto;
  left: 2px;
}

/* ─────────────────────────────────────────────────────────────
   Split layout (left panel + map)
   ───────────────────────────────────────────────────────────── */
.split {
  display: grid;
  grid-template-columns: 3fr 2fr;
  /* One windowful tall, but never taller than the design height — see
     --page-max-h. Without the cap, zooming out gave the panels more CSS
     pixels of height than their contents grow into (the vh clamps inside are
     already maxed at ~1080p), so the rows just drifted apart.
     --sheet-h resolves to exactly that (see :root): the window's height in the
     sheet's own scaled pixels under the auto-fit zoom, falling back to the vh
     calc with no JS or no zoom support. */
  height: var(--sheet-h);
  /* Reads as a sheet on the canvas once the caps leave space around it, and
     is off-screen — invisible — when the content fills the window.
     .panel--dark's grey matches the canvas, so this is what marks the edge. */
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07);
  /* No min-height: on a landscape phone (~393px tall) a 520px floor exceeds
     the viewport, so the panel overflows and any flex:1 child inside it has
     nothing to resolve against — which is how the charts grew unbounded. */
  min-height: 0;
}

/* Restore the floor only where there is room for it. The floor belongs to the
   sheet, not to .split — .split is only the part of it below the header (see
   .slide above). */
@media (min-height: 600px) {
  .split { min-height: 520px; }
  .slide { min-height: 520px; }
}


/* ─────────────────────────────────────────────────────────────
   Oppsummering's scrolling left column
   The slide used to be a vertical stack — banner, a bounded cards-beside-map
   row, then the daily-production chart full width under it, deliberately
   taller than the sheet so the chart ran past the fold. That put the chart out
   of reach of the two controls that drive it: scroll down to read it and the
   map and the header pie, which is how you pick its region, are off the top of
   the screen. So the slide is now shaped like every other one — the row fills
   the sheet, the map runs to the bottom — and the chart went into the left
   panel underneath the cards, where it scrolls with them.
   .split--bidrag survives only as a hook for the phone overrides further
   down; the desktop row is the plain .slide > .split above.
   ───────────────────────────────────────────────────────────── */

/* The chart's own band inside that column. Still a fixed height — a clamp, so
   the composition holds under the auto-fit zoom — because Chart.js needs a
   definite one to draw into, and the column around it scrolls rather than
   handing out a share of itself. */
.bidrag-ts-row {
  height: clamp(300px, calc(52 * var(--sheet-vh)), 560px);
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  /* Side inset from the shared token, so the plot's left and right edges land
     on the KPI cards' above it. It was a hand-written 28px, which put it 8px
     in from them on each side. */
  padding: 0 var(--slide-body-pad-x) 28px;
}

/* ── Småkraftåret, as a sentence ────────────────────────────────────────────
   Under the production plot in the scrolling column, so it costs the chart and
   the map nothing. A card, not a bare paragraph: it is a distinct reading of
   the year rather than a caption on the chart above it.
   Side margin from --slide-body-pad-x, the same token the plot above and the
   KPI grid above that take, so all three stack to one width. It was a
   hand-written 28px matching the plot's own hand-written 28px, which meant the
   two agreed with each other and both disagreed with the cards.
   flex-shrink:0 because the column scrolls: this keeps its height instead of
   giving it up to the chart (same reason .kons-cases carries it). */
.aaret-story {
  flex-shrink: 0;
  margin: 0 var(--slide-body-pad-x) 28px;
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
  padding: 18px var(--kpi-inset) 20px;
}
.aaret-story[hidden] { display: none; }

/* Header with title and menu */
.aaret-story__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

/* The card's own name, at the same size every other card title on this slide
   uses — see the --fs-kpi-* note in :root: this page is built from three sizes
   and a fourth here would be the same slip the chart heading used to be. */
.aaret-story__eyebrow {
  font-size: var(--fs-kpi-title);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0;
  flex-grow: 1;
}

/* The line under each slide's title, saying what the page is for. One per
   slide, in the header beside the pie (see .utbygging-header-left), so a reader
   arriving on a tab is told what they are looking at before they read a figure.
   --fs-kpi-meta, which is the size this page gives every "sentence that
   explains the numbers": the story card's body, the KPI context lines. A fourth
   size here would be the drift the three-size scale exists to prevent.
   The measure is capped because a header runs the full width of the sheet and
   continuous prose at that length is hard to track back from the end of one
   line to the start of the next. It also keeps the line to two rows on a wide
   screen, which is what bounds how much height the header takes from the panels
   below it. */
.slide-intro {
  /* Rides --head-condense down to 0 with height and opacity below, so the
     line closes at exactly the scroll's own pace instead of vanishing the
     instant a fixed offset is crossed (display:none, before this). */
  margin: 0;
  margin-top: calc(6px * (1 - var(--head-condense, 0)));
  opacity: calc(1 - var(--head-condense, 0));
  /* Divided by --head-fit, which is the zoom the header carries (see
     applyPageZoom). Everything in this band is drawn smaller than its own px
     value by that factor, so a plain --fs-kpi-meta rendered visibly smaller
     than the very same token does out in the sheet, on "Tilsvarer … traktorer"
     and "Nok til å dekke …". Dividing it back out lands this line at exactly
     that size on screen, whatever the header is scaled to, instead of a
     hand-picked number that would only match at one window size. */
  font-size: calc(var(--fs-kpi-meta) / var(--head-fit, 1));
  line-height: 1.4;
  color: var(--text-muted);
  /* ── One line, always ───────────────────────────────────────────────────
     It used to be two, with the height reserved whether the sentence needed
     it or not: that was what kept every slide's title on the same line, since
     the title column is centred against the header's tallest child (see
     .bidrag-header .utbygging-header-left) and a column that is shorter on one
     slide drops its title lower than the rest. One reserved line does exactly
     the same job (the alignment only needs every slide to agree, not to agree
     on two) and hands the sheet back a line of header on every tab.
     1.4em = one line at the line-height above, in this element's own
     font-size, so it follows the size calc rather than needing its own. A
     fixed height rather than min-height: min alone only floors the box, and
     once --head-condense starts eating that floor the actual text (one line,
     forced by nowrap below) would keep drawing at full height regardless —
     height doubles as the ceiling that lets it close.
     nowrap and the ellipsis are what hold the promise: the sheet is a fixed
     1920-wide canvas on the desktop (applyPageZoom scales it, the measure does
     not change), so these sentences have a constant ~1500px to run in and all
     of them fit, but a description grown past that shortens with an ellipsis
     rather than silently taking a second line back and pushing every title on
     that slide out of step. The phone block below undoes all three: there the
     column is 360px wide, nothing fits on one line, and there is no row of
     titles left to keep aligned. */
  height: calc(1.4em * (1 - var(--head-condense, 0)));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The live figure inside Kraftpriseffekt's line (see #kp-intro-mw). Picked out
   the same way the Småkraftåret sentence picks its numbers out, so the sentence
   can be skimmed for the figure as well as read. */
.slide-intro b {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* The region name, set off in the same pale-blue chip used by the region
   picker button elsewhere (aaret-title__region) — no em dash needed once the
   region has its own visual boundary. */
.aaret-story__region-badge {
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  margin-left: 10px;
  font-size: 0.7em;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--brand-blue);
  background: var(--tint-blue);
  border-radius: 8px;
  padding: 4px 12px;
}

/* Running text, at the meta size — this is the "sentence that explains the
   figures" line, which is exactly what --fs-kpi-meta is for. Wider line height
   than a card's context line because this one actually wraps to several lines,
   and a max-width because a 900px-wide measure of continuous prose is hard to
   track back from the end of one line to the start of the next. */
.aaret-story__body {
  font-size: var(--fs-kpi-meta);
  line-height: 1.6;
  color: var(--text-muted);
  max-width: 68ch;
}
/* The figures themselves, picked out of the sentence so the paragraph can be
   skimmed for numbers as well as read for sense. */
.aaret-story__body b {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ── Left panel ── */
/* No padding of its own — .bidrag-header and .bidrag-grid each carry their
   own, same split as .panel--aaret/.aaret-header/.aaret-body, so the header
   can sit genuinely edge-to-edge (full-width white strip) instead of inset
   within a padded panel. */
.panel--dark {
  position: relative;
  background: var(--tint-grey);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: 0;
  overflow: hidden;
}
/* Oppsummering's is the scrolling one: the KPI grid and the daily-production
   chart under it come to more than the sheet, and that is the point — the
   column can take more than fits while the map beside it stays whole. Only
   this slide's, because .panel--dark is also Kraftpris', where .kp-body-wrap
   does its own scrolling one level in. */
#slide-bidrag .panel--dark {
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

/* ── Browser zoom belongs to the scrolling columns ──────────────────
   Zooming out used to do nothing useful on this dashboard: --page-zoom capped
   at 1, so a wider viewport just left the sheet at design size with grey around
   it — the map and the titles got smaller and the column still scrolled past
   exactly the same fold, which is the one thing you zoom out to fix. The two
   are now separate axes:

     - the sheet scales to fill the window (applyPageZoom no longer caps at 1),
       so the maps and the slide titles hold their size on screen whatever the
       browser zoom is. There is nothing to gain by zooming those, and now
       nothing happens to them either.
     - the columns beside them answer to the browser zoom, one page at a time.
       --col-fit is set per element by fitScrollColumns (script.js): as much of
       the zoom-out as that column needs to bring its content onto one page and
       no more, so a column that already fits never shrinks at all. Unset (no
       JS, or nothing to gain) it falls back to 1 and nothing here applies.

   No width to go with the zoom, deliberately: the column keeps filling its half
   of the sheet, so what a zoomed-out step buys is vertical. The box grows in the
   column's own pixels, the cards spread into the extra width and so come out
   shorter, the type gets smaller with them — and the chart underneath, which is
   what was below the fold, moves up into the room that frees. Nothing is
   centred in a narrower measure with grey down the sides.

   Only the columns that can hold more than fits. The map panels fill their
   sheet by construction, so shrinking them would cost size and reveal nothing.
   .utbygging-body used to be in that category and no longer is: the
   utbyggingskostnader block sits under its chart, so it has something below the
   fold to bring up like the rest of them. */
#slide-bidrag .panel--dark,
.aaret-body,
.kp-body-wrap,
.utbygging-body {
  zoom: var(--col-fit, 1);
}

/* ── Chapter heading and chapter rule ────────────────────────────────────────
   Samfunnseffekter's left column carries three subjects in one scroller — what
   the supplier industry makes of småkraft, what the public purse takes, and
   what falls to private owners — and these two rules are what tell them apart.
   The heading is black and heavier than the green .kpi-section-label under it,
   so the pair reads as chapter then subject rather than as two labels of equal
   weight. Shared classes rather than per-section ones: the whole point is that
   the three openings look identical.
   .sk-block-title was .lev-section__title, which only the middle section
   had; .lev-section was the same three declarations inline on .lev-section. */
  /* Section titles run at --fs-kpi-meta, the size of the front page's
     "Nok til a dekke arsforbruket til ..." line. Set as the TOKEN, never its
     22px: it is redefined at the phone breakpoint and a literal would stop
     following. See the KPI type-scale note at the top of this file. */
.sk-block-title {
  margin: 0 0 18px;
  font-size: var(--fs-kpi-meta);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text);
}

.lev-section {
  margin-top: 30px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}

/* ── The body of a slide's left column — ONE rule, four slides ───────────────
   Every slide is the same two pieces: a white header strip (.aaret-header /
   .utbygging-header / .bidrag-header, already grouped below) and a scrolling
   grey body under it. The body was written out four times, and the four copies
   had drifted: Utbygging sat at 16/20 while the other three sat at 24/28, so
   its content started 8px further up and to the left of everyone else's and no
   two slides quite lined up. This is the one definition; the padding is a token
   so changing the inset is one edit rather than four.

   Utbygging's tighter 16/20 is what all four now use — it is the slide these
   were meant to match, and the columns scroll, so the pixels the inset was
   spending are pixels of content.

   Each of the four still has a rule of its own further down, for what is
   genuinely its own: whether it is a grid or a flex column, and the two that
   line their bottom edge up with the map card's inset. Those come later in the
   file and so win where they overlap — deliberately. */
/* The side inset is its own token because three things on Oppsummering have to
   share it and did not: the KPI grid takes it as padding, while the daily
   production plot (.bidrag-ts-row) and the Småkraftåret box (.aaret-story) sit
   OUTSIDE that grid as siblings and carried a hand-written 28px each. Eight px
   wider on both sides than the cards above them, which is exactly enough for
   the column to read as two different widths stacked. Composed into the
   shorthand rather than the other way round, since only the horizontal half is
   shared. */
:root {
  --slide-body-pad-x: 20px;
  --slide-body-pad: 16px var(--slide-body-pad-x);
}

.bidrag-grid,
.aaret-body,
.kp-body-wrap,
.utbygging-body {
  background: var(--tint-grey);
  flex: 1;
  overflow-y: auto;
  padding: var(--slide-body-pad);
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

/* The slide titles come along at half the rate (--head-fit, applyPageZoom).
   They hide nothing, so there is nothing to reveal by shrinking them — but the
   band is 100-odd pixels the column below could be using, and a slightly
   smaller title still reads as the title. */
.bidrag-header,
.aaret-header,
.utbygging-header {
  zoom: var(--head-fit, 1);
}

/* ── Condensed: the header once you have started reading ─────────────────────
   Whichever thing scrolls on this layout (the column beside the map on the
   desktop, the page itself on a phone), syncHeadCondense (script.js) tracks
   how far into that scroll the reader is as --head-condense: a 0..1 custom
   property on <body> that moves in exact lockstep with the scroll offset over
   a fixed pixel range, rather than a threshold that flips a class and leaves
   a canned CSS transition to guess the timing. What goes is everything that
   introduces the slide: the description, and the pie's caption and
   percentage. What stays is everything that identifies or controls it: the
   title, the region chip inside it, the geo toggle, and the pie itself, which
   is a region picker, not an illustration, so it shrinks rather than leaving.
   The point is the space: this hands the column up to 60-odd pixels back,
   which on the desktop is most of a KPI card's worth of the fold. The .split
   below takes the room (it is flex:1), so the map grows with it in step,
   frame by frame, rather than once a threshold is crossed: see the
   invalidateSize in syncHeadCondense, which now runs on every frame the
   scroll moves through this range instead of once after it settles.
   `body` in front of each selector below is a specificity bump, not a scoping
   need: every one of these has to outrank that header's own
   `padding: 8px 28px 6px` shorthand (.bidrag-header etc., declared later in
   the file, see there) on the padding-top it also sets. The old
   .is-head-condensed rule got that win from JS toggling a second class;
   this way the same win comes from the selector alone, for whatever value
   --head-condense carries. */
body .bidrag-header,
body .aaret-header,
body .utbygging-header {
  padding-top: calc(8px - 2px * var(--head-condense, 0));
}

/* The two geography buttons stack on top of each other while the header is
   full height, which is the arrangement that keeps them narrow beside the pie.
   Condensed there is nothing to be narrow for (the band is a strip with a
   title at one end) and side by side they cost one row instead of two, which
   is what the pie's 56px would otherwise have been wasted against. Kept as a
   threshold flip, not a continuous one: a row/column swap has no in-between
   state for calc() to interpolate through. */
.is-head-condensed .bidrag-header > .geo-toggle,
.is-head-condensed .aaret-header > .geo-toggle,
.is-head-condensed .utbygging-header > .geo-toggle {
  flex-direction: row;
  align-items: center;
}

/* Everything in the big view is dark ink. The column moved in here keeps its
   own classes, and .kpi-section-label is coloured per slide GROUP (orange on
   ring, green on verdi, brand blue elsewhere — see the two rules on
   .slide[data-group]); in here there is no slide above it to take a colour
   from, and the four groups' cards are side by side anyway, so a label in any
   one group's colour would be miscolouring the other three. Black reads as the
   heading it is. */
#sk-zoom .kpi-section-label { color: var(--text); }

/* Faint brand mark watermark, bottom-right */
.panel--dark::after {
  content: '';
  position: absolute;
  right: -60px;
  bottom: -70px;
  width: 340px;
  height: 340px;
  background: url('assets/smakraft-mark.png') no-repeat center / contain;
  opacity: 0.06;
  pointer-events: none;
  z-index: 0;
}

.panel--dark > * { position: relative; z-index: 1; }

/* ── Right map panel ── */
.panel--map {
  position: relative;
  background: var(--tint-grey);
  border-left: 1px solid var(--border);
  padding: clamp(10px, calc(1.5 * var(--sheet-vh)), 20px) clamp(12px, 2vw, 24px);
  display: flex;
  flex-direction: column;
}

.map-card {
  flex: 1;
  min-height: 0;
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.10);
  overflow: hidden;
  position: relative;
}

#map,
#map-aaret,
#map-utbygging,
#map-leverandor,
#map-verdier,
#map-grunneiere,
#map-kraftpris,
#map-konsesjons {
  width: 100%;
  height: 100%;
}

.map-hint {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 11px;
  color: rgba(255,255,255,0.3);
  pointer-events: none;
  white-space: nowrap;
}

/* ─────────────────────────────────────────────────────────────
   Map overrides (no tile layer — dark canvas)
   ───────────────────────────────────────────────────────────── */
.leaflet-container {
  background: #fff;
}

/* Fylke callout popup */
.fylke-popup .leaflet-popup-content-wrapper {
  background: #fff;
  border-radius: 6px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.35);
  padding: 0;
  overflow: hidden;
}

.fylke-popup .leaflet-popup-content {
  margin: 0;
  width: auto !important;
}

/* Hide default Leaflet tip — leader line is drawn by JS */
.fylke-popup .leaflet-popup-tip-container {
  display: none;
}

.fylke-popup .leaflet-popup-close-button {
  color: var(--text-muted);
  top: 6px;
  right: 8px;
  font-size: 16px;
  line-height: 1;
}

/* Popup inner layout */
.fp__name {
  background: var(--green-800);
  color: #fff;
  font-family: 'Satoshi', sans-serif;
  font-size: 14px;
  padding: 10px 14px 8px;
}

.fp__rows {
  padding: 10px 14px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.fp__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}

.fp__label {
  font-size: 10px;
  color: var(--text-muted);
  white-space: nowrap;
}

.fp__val {
  font-family: 'Satoshi', sans-serif;
  font-size: 15px;
  color: var(--green-800);
  white-space: nowrap;
}

.fp__unit {
  font-family: 'Satoshi', sans-serif;
  font-size: 10px;
  font-weight: 600;
  color: var(--green-600);
}

/* ─────────────────────────────────────────────────────────────
   Persistent fylke labels (dot → line → card)
   ───────────────────────────────────────────────────────────── */
/* The marker box is FL_TOT x FL_H (187x96) and is pinned to a fixed latlng, so
   it drifts over land as soon as the map is panned or zoomed. Nothing here is
   interactive (every card marker is created with interactive: false), so it must
   never capture clicks — otherwise it swallows them and the fylke polygon
   underneath can no longer be selected. Same rule as .kv-dock. */
.fl-marker {
  background: none !important;
  border: none !important;
  overflow: visible !important;
  pointer-events: none;
}

.fl-label {
  display: flex;
  align-items: center;
  gap: 4px;
  pointer-events: none;
}

.fl-label--right { flex-direction: row; }
.fl-label--left  { flex-direction: row-reverse; }

.fl-label__dot {
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #fff;
  border: 1.5px solid var(--green-400);
  z-index: 1;
}

/* Permanent per-region count badge (Konsesjon, Strøm view) — one per fylke/
   sone, centered on the shape, printing the exact count the choropleth fill
   already encodes as color. Non-interactive, same rule as .fl-marker: must
   never swallow the click meant for the polygon underneath. */
.kons-count-marker {
  background: none !important;
  border: none !important;
  overflow: visible !important;
  pointer-events: none;
}
/* Bare numerals on the map — the white pill these used to sit in read as a box
   stuck beside every region. Legibility over the choropleth comes from a halo
   drawn with text-shadow instead, which costs no visual footprint. */
.kons-count-badge {
  display: inline-block;
  transform: translate(-50%, -50%);
  white-space: nowrap;
  color: var(--text);
  font-family: 'Satoshi', sans-serif;
  font-size: 11px;
  font-weight: 700;
  text-shadow:
     0 0 3px #fff,  0 1px 2px #fff, 1px 0 2px #fff,
     0 -1px 2px #fff, -1px 0 2px #fff;
}

.kons-count-badge--plate {
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid rgba(21, 38, 59, 0.12);
  border-radius: 999px;
  padding: 2px 7px;
  text-shadow: none;
}

/* Card: opaque light-blue background */
.fl-label__card {
  pointer-events: none;
  min-width: 175px;
  max-width: 175px;
  background: #e8f0f9;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.45);
  border-radius: 6px;
  overflow: hidden;
}

/* Override fp__ rows background to match card */
.fl-label__card .fp__rows {
  background: #e8f0f9;
}

/* Name header stays dark green */
.fl-label__card .fp__name {
  background: var(--green-800);
  color: #fff;
}

/* Text colours adjusted for light background */
.fl-label__card .fp__val  { color: var(--green-800); }
.fl-label__card .fp__unit { color: var(--green-600); }

/* ── Orange theme (Konsesjon, Utbygging) ── */
.fl-label__dot--orange { border-color: #e07b00; }
.fl-label__card--orange { background: #fff5ed; }
.fl-label__card--orange .fp__rows { background: #fff5ed; }
.fl-label__card--orange .fp__name { background: #b84700; color: #fff; }
.fl-label__card--orange .fp__val  { color: #b84700; }
.fl-label__card--orange .fp__unit { color: #e07b00; }

/* ── Green theme (Leverandørindustri) ── */
.fl-label__dot--green { border-color: #3fa32a; }
.fl-label__card--green { background: #eaf7e3; }
.fl-label__card--green .fp__rows { background: #eaf7e3; }
.fl-label__card--green .fp__name { background: #1f5c00; color: #fff; }
.fl-label__card--green .fp__val  { color: #1f5c00; }
.fl-label__card--green .fp__unit { color: #3fa32a; }

/* ── Blue theme (Småkraftåret) ── */
.fl-label__dot--blue { border-color: #2f7dd1; }
.fl-label__card--blue { background: #e8f3fc; }
.fl-label__card--blue .fp__rows { background: #e8f3fc; }
.fl-label__card--blue .fp__name { background: #0050a1; color: #fff; }
.fl-label__card--blue .fp__val  { color: #0050a1; }
.fl-label__card--blue .fp__unit { color: #2f7dd1; }

/* ─────────────────────────────────────────────────────────────
   Stats panel content
   ───────────────────────────────────────────────────────────── */

/* Live-dot eyebrow */
.eyebrow {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--text);
  margin-bottom: clamp(4px, calc(1.2 * var(--sheet-vh)), 16px);
  line-height: 1.5;
}

@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%       { opacity: 0.4; transform: scale(1.5); }
}

/* ── Real-time effekt KPI (half-donut) ── */
.effekt-kpi {
  margin: 12px 0 16px;
  padding-top: 14px;
  border-top: 1px solid rgba(0,0,0,0.07);
}
.effekt-kpi__eyebrow {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  margin-bottom: 7px;
}
.effekt-kpi__value-row {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-bottom: clamp(4px, calc(0.8 * var(--sheet-vh)), 10px);
}
/* ── The waiting mark ────────────────────────────────────────────────────────
   A dam with water running through it, in the same 24-grid stroke language as
   the sprite above (1.8 stroke, round caps). It is a dam because that is what
   the page is about, and because a figure that is genuinely still being
   computed deserves something other than the same spinner as everything else.

   The base size is in em so the mark can stand anywhere text stands without
   changing a line's height; .income-box.is-loading below overrides it with a
   fixed size, because in the middle of a card it answers to the card and not
   to any particular line of type. */
.dam-loader {
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.12em;
  color: var(--text-muted);
  flex: none;
}
/* Each wave runs the same 1.5s cycle, a third apart, so the gate is never
   empty: one wave is always on its way out while the one before it fades.
   translate on the path itself (not the group) so the three can be offset
   independently; the group only exists to select them. */
/* The water gets the page's own blue rather than inheriting the wall's grey.
   At this size the shape alone is ambiguous (three grey waves between two grey
   posts could be almost anything); the colour is what makes it water passing
   through a dam at a glance, and it is the same blue the production figures
   use everywhere else on the page. */
.dam-loader__flow path {
  stroke: var(--brand-blue-light);
  animation: dam-flow 1.5s linear infinite;
  opacity: 0;
}
.dam-loader__flow path:nth-child(2) { animation-delay: 0.5s; }
.dam-loader__flow path:nth-child(3) { animation-delay: 1s; }
/* The travel is in USER UNITS, not px: the transform is on an element inside a
   24-unit viewBox, so 8 here is a third of the drawing's height whatever size
   the em works out to. It enters just under the crest and leaves at the foot of
   the walls. */
@keyframes dam-flow {
  0%   { transform: translateY(0);   opacity: 0; }
  20%  { opacity: 1; }
  75%  { opacity: 1; }
  100% { transform: translateY(8px); opacity: 0; }
}
/* Motion is the whole content of this mark, so there is nothing to soften: it
   stops, and the water sits in the gate as a still drawing that still says
   "water leaving a dam". */
@media (prefers-reduced-motion: reduce) {
  .dam-loader__flow path { animation: none; opacity: 1; }
  .dam-loader__flow path:nth-child(2),
  .dam-loader__flow path:nth-child(3) { display: none; }
}
/* ── A card while it waits ──────────────────────────────────────────────────
   The mark sits over the middle of the card and the card's own contents are
   taken off it, leaving the heading so you can still tell which card is
   waiting. Everything else, the figures, the donut, the bar, the "tilsvarer"
   lines, is placeholder text and empty boxes at this point, and a page of
   half-drawn cards behind four loaders reads as clutter.

   visibility, not display, and that is the point of doing it this way: a hidden
   box still takes its space, so nothing reflows when the figures arrive and the
   column around it is not re-measured (see fitScrollColumns, which is what used
   to make the whole page step down a size mid-load). visibility also inherits,
   which is what lets one rule take the card away and a second rule bring the
   heading and the overlay back.

   pointer-events stays off the overlay so the card underneath keeps working as
   the chart switch it is (role="button" on both card types). */
.income-box,
.bidrag-cell--effekt,
.bidrag-cell--ytd { position: relative; }

/* The mark's own layout lives here; each host below decides whether it shows
   and where it sits. display:none is the default so a stamped placeholder that
   is waiting for its host's state (the four KPI cards) stays out of the way. */
.card-loading {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  pointer-events: none;
}

.income-box.is-loading > *,
.bidrag-cell.is-loading > * { visibility: hidden; }

.income-box.is-loading .effekt-kpi__eyebrow,
.bidrag-cell.is-loading .effekt-kpi__eyebrow,
.income-box.is-loading > .card-loading,
.bidrag-cell.is-loading > .card-loading { visibility: visible; }

/* Over the card: the card keeps its own size and the mark is laid across it. */
.income-box.is-loading > .card-loading,
.bidrag-cell.is-loading > .card-loading,
.map-card > .card-loading {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
}

/* In the flow: here the mark IS the box, because its host has no size of its
   own until the thing it is waiting for arrives. The height is what the header
   pie comes out at once drawn (78px disc, its label and its percentage, plus
   the wrap's own padding), so the row does not change depth when the real one
   replaces this. A phone puts the mini fylke map in the same row and that is
   the taller of the two, so the height is restated in the phone block. */
.card-loading--inflow {
  display: flex;
  position: static;
  width: 100%;
  height: 112px;
}
.card-loading__text {
  font-size: var(--fs-kpi-meta);
  font-weight: 700;
  color: var(--text-muted);
  letter-spacing: 0.01em;
}
/* Fixed, not sized off any line of type: in the middle of a card the mark
   answers to the card. 34px is about the caption under it doubled, which is
   what makes the two read as one mark. */
.is-loading .dam-loader {
  width: 34px;
  height: 34px;
  vertical-align: baseline;
}

.effekt-kpi__value {
  font-size: var(--fs-kpi-value);
  font-weight: 900;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--text);
  /* Ticking values (income, effekt, GWh) update every second; without this,
     proportional-width digits (e.g. "1" narrower than "8") make the centered
     text visibly wiggle side to side on every tick even though nothing
     meaningful changed. */
  font-variant-numeric: tabular-nums;
}

/* GWh sub-unit precision, styled like a mathematical exponent: smaller and
   raised, instead of a decimal comma that reads as extra magnitude (e.g.
   "1600,165" misread as ~1.6 million). Represents whole MWh (3 digits). */
.gwh-frac {
  font-size: 0.42em;
  font-weight: 800;
  vertical-align: super;
  line-height: 0;
  margin-left: 2px;
  font-variant-numeric: tabular-nums;
}
.effekt-kpi__unit {
  /* The meta size, not a 13px of its own: this slide uses exactly three sizes
     — the figure (--fs-kpi-value), the card's name (--fs-kpi-title) and the
     sentence-that-explains-the-figure (--fs-kpi-meta) — and the unit belongs to
     the last of those. Still plainly subordinate next to a 40px figure, which
     is all the old 13px was buying. */
  font-size: var(--fs-kpi-meta);
  font-weight: 700;
  color: var(--text-muted);
}
#effekt-donut {
  display: block;
}

/* ── Bidrag slide: the four KPI cards ──
   The timeseries that used to be a third row of this grid is its own band
   below it now (.bidrag-ts-row), further down the same scrolling column. */
.bidrag-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* Three equal rows: Skatteinntekter, Grunneierinntekter, then the two
     production cards side by side — so all four boxes are exactly one row
     tall and no card reads as the runt of the grid. It used to be `auto 1fr`,
     which sized the income pair to their content and handed the production
     cards whatever was left; once the income boxes were scaled up that
     remainder was less than a row, and effekt/YTD came out visibly short.
     Equal rows need the two income boxes to BE rows, which is what the
     display:contents on their wrapper below is for.
     1fr, not minmax(0, 1fr): a row still refuses to go under its content's
     min-height, so an overlong card overflows the grid (which scrolls)
     instead of spilling over the card beneath it. */
  grid-template-rows: repeat(3, 1fr);
  column-gap: 10px;
  row-gap: 10px;
  /* Sized by its content now that the column around it scrolls, not by a share
     of a fixed-height panel. The three 1fr rows still come out equal: against
     an indefinite height they resolve to the tallest row's content, which is
     what keeps all four boxes the same height. */
  flex: none;
  min-height: 0;
  /* Inset and surface come from the shared slide-body rule near the top of this
     file — this IS the "body below the header" element on Oppsummering, so it
     takes the same one every other slide does rather than restating it. */
}

.bidrag-cell {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

/* KPI box cards */
.bidrag-cell--effekt,
.bidrag-cell--ytd {
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.08);
  padding: clamp(8px, calc(1.1 * var(--sheet-vh)), 14px) 16px
           clamp(8px, calc(1.1 * var(--sheet-vh)), 14px) var(--kpi-inset);
  color: var(--text);
  /* stretch, not center: the card's rows (heading, figure row, context line)
     each span its full width and align their own contents to the left. Centred,
     every row was shrink-wrapped to its text and floated in the middle. */
  align-items: stretch;
  text-align: left;
  /* These two share the grid's 1fr row and so are taller than their
     contents need. Centre them VERTICALLY rather than letting the figure sit
     against the top edge with empty card beneath it — horizontally everything
     stays left, see text-align above. */
  justify-content: center;
}

/* Income cell spans both grid columns (kraftpris box removed) but, unlike the
   effekt/ytd cards, is not itself a card — it is a plain layout wrapper
   around two separate boxes (.income-box), one per income stream, so Skatte-
   and Grunneierinntekter read as distinct rather than as one combined card.
   Nor is it a control: each .income-box carries its own .kpi-switch, since the
   two streams now plot separately (see the hover/active rules further down). */
.bidrag-cell--income {
  /* Not a box of its own — display:contents lifts the two .income-box
     children into .bidrag-grid as real grid items, which is what lets them
     take a grid row each and so match the production cards' height exactly.
     The element stays in the DOM, so the .bidrag-cell--income descendant
     rules further down still match; its own gap is gone, replaced by the
     grid's identical 10px row-gap. */
  display: contents;
}
/* Each income box is now a full-width grid row of its own. min-height: 0 so
   it can be sized by its 1fr row rather than flooring the row at its content
   height, same as the other grid cells. */
.income-box {
  grid-column: 1 / -1;
  min-height: 0;
}

/* Oppsummering title banner. Unlike .aaret-header on the other slides, this
   one is NOT inside the left panel — it is a direct child of the slide (see
   #slide-bidrag in index.html). Inside .panel--dark it ended at the 3fr/2fr
   seam, which read as a title cut off by the map; out here it runs the full
   width of the sheet and the map starts below it. Sized to its own content
   (no min-height floor), same as .aaret-header/.utbygging-header. */
.bidrag-header {
  background: #fff;
  border-bottom: 1px solid var(--border);
  /* 8/6, down from 22/20. See the note on .aaret-header's padding for why
     the three headers change together. */
  padding: 8px 28px 6px;
  box-sizing: border-box;
  flex-shrink: 0;
  /* Header pie sits to the right, same row as the title/toggle column —
     see .utbygging-header-left / .utbygging-pie-wrap for the shared shape. */
  display: flex;
  align-items: flex-start;
  gap: 16px;
  /* Same reason as .aaret-header: the region-panel dropdown lives inside this
     header, and the .split below is a later sibling, so it would win the tie
     on paint order and cover the open panel. */
  position: relative;
  z-index: 10;
}

/* The title column centres against the header's tallest child — the 136px pie
   — instead of starting at its top edge, so the title sits on the pie's own
   axis rather than riding above it. All three header classes, so the title
   sits at the same height on every slide: a title that jumps up and down as
   you flip through the slides is the thing you notice. Only the title column:
   the geo-toggle keeps its top alignment beside the pie, which is what pairs
   the two as one control. Every column is title-only now that Utbygging and
   Konsesjon have handed their KPI pills down to their chart cards, so the pie
   is reliably the tallest child and this centres against it on all seven
   slides. On phones .utbygging-header-left is display:contents (see the
   stacked-header block in the media query), so there is no box for this to
   apply to and the stacked order is untouched. */
.bidrag-header .utbygging-header-left,
.aaret-header .utbygging-header-left,
.utbygging-header .utbygging-header-left { align-self: center; }

/* ── Same height as every other slide header ──────────────────
   This header used to be laid out as a shallow bar of its own: 16px padding,
   everything vertically centred, and the pie scaled down to 98px with its
   caption moved beside it rather than under it. That bought ~85px of sheet
   back, but it also made Oppsummering the one slide whose title block is a
   different depth from the rest — and since the slides are flipped through in
   sequence, a title bar that changes height between them is the thing you
   notice. So the bar overrides are gone and this header takes exactly the
   shape .aaret-header/.utbygging-header take: the shared 22/20 padding, the
   toggle stacked beside a full-size 136px pie (the stacking rule already
   names .bidrag-header — see `.bidrag-header > .geo-toggle` further down),
   and the caption under the pie. Identical content shape means identical
   height, with no min-height floor to keep in sync.
   The only thing still specific to this header is where it sits: outside the
   panel, spanning the sheet (see the block above). */

/* Skatte- and Grunneierinntekter each get their own card — same white/shadow
   treatment as the effekt/ytd cards below, just not centered: a left-aligned
   title reads as "this box's name", not a caption under a number. */
.income-box {
  background: #fff;
  border-radius: 10px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.08);
  /* Each box is a full 1fr grid row holding a single ticker row, so there is
     slack above and below its content. Split that evenly rather than spending
     it all under the content: the figure then reads as sitting IN the card
     instead of pinned to its top edge. The headings no longer start at the
     same depth as the effekt/YTD cards' below — those are top-aligned in a
     taller card — but the two income figures now centre on their own boxes,
     which is the axis that matters when the boxes are read as a pair. */
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* No horizontal padding: the ticker inside has to span exactly the same
     width as the effekt card below it (outer grid column 1) for the two
     centred titles to sit on one axis — see .income-ticker. Its own 22px
     inset keeps the text off the card edge.
     Vertical padding is symmetric now — it is a floor for the short-card case,
     not a top inset. With the old 6/21 split, centring inside the content box
     would have put everything ~7px above the card's true middle. */
  padding: clamp(6px, calc(1 * var(--sheet-vh)), 12px) 0;
  color: var(--text);
  transition: box-shadow 0.15s ease;
}
/* Both income boxes: the ticker tile left, its "tilsvarer" equivalent filling
   the rest of the row, so the sentence starts right after the ticker instead
   of hugging the box's right edge. That gives both equivalents the same left
   edge however long the sentence runs — "3 traktorer" no longer floats in from
   further right than "barnetrygd for 1 200 barn i ett år" above it. No
   justify-content needed: the equivalents grow into the remainder themselves.
   The per-box .income-box__title above this row is gone — the eyebrow inside
   the ticker already names the figure. */
.income-box__row {
  display: flex;
  /* Only governs the ticker now: it is the taller child, so it sets the row
     height and flex-start is where it already sits. The sentence beside it
     opts out with its own align-self — it has to line up with the figure at
     the BOTTOM of the ticker stack, not the title at the top. The phone rule
     overrides this to stretch: stacked, this axis becomes the horizontal
     one. */
  align-items: flex-start;
  /* The outer grid's column-gap, so the ticker and the equivalents land on the
     same two columns the effekt and YTD cards occupy. */
  gap: 10px;
}

/* The ticker itself: no tile any more. The blue fill was a box inside a box —
   the .income-box card already separates the figure from the page, so the tint
   only drew a second frame around the number. Untinted, the figure sits on the
   card and the card is the container; the padding is what still holds it to
   its own half of the row. */
.income-ticker {
  background: transparent;
  border-radius: 8px;
  /* No vertical inset: it belonged to the tinted tile this used to be, and
     with the tint gone it only pushed the heading off the top of the card. */
  /* Asymmetric again: the 34px left inset clears the (i) dot, which sits at
     the card's own top-left corner (8px in, 20px wide) and would otherwise
     collide with the heading now that the heading starts at the left edge.
     While the stack was centred a symmetric 22px was enough. */
  padding: 0 22px 0 34px;
  /* Left-aligned stack: title, rule, figure down a single left edge — the
     same axis the effekt and YTD cards in this grid already use, so all four
     read the same way. The rule sits between the heading and the number and
     keeps that left edge (see .income-ticker__rule) rather than centring
     under the figure, which is what it did while the label was a caption. */
  text-align: left;
  /* Exactly outer grid column 1: (100% - 10px column-gap) / 2. Plain 50% put
     the ticker's centre ~14px right of the effekt card's, so "Skatteinntekter"
     and "Nåværende småkraftproduksjon" read as almost-but-not-aligned. */
  flex: 0 0 calc(50% - 5px);
}
/* Only the margin and the leading are the ticker's own now. The size comes
   from --fs-kpi-title with the effekt and YTD headings (see the rule further
   down that names all three cells): the label heads its card exactly the way
   "Nåværende småkraftproduksjon" heads the one beside it, so the four cards in
   this grid read as one set rather than two big titles and two small ones.
   It used to sit at a caption clamp, from when it was a caption UNDER the
   figure rather than a heading over it. */
.income-ticker .effekt-kpi__eyebrow {
  margin-bottom: 0;
  line-height: 1.25;
}
.income-ticker .effekt-kpi__value-row { justify-content: flex-start; margin-bottom: 0; }

/* The short rule between the label and the figure. Sized in ems so it tracks
   the label above it rather than sitting at a fixed width when the card
   scales; a 0 left margin (not auto) keeps it on the stack's left edge. */
.income-ticker__rule {
  display: block;
  width: 2.4em;
  height: 3px;
  margin: 9px 0 8px;
  background: var(--text);
  border-radius: 2px;
}

/* The "tilsvarer" equivalents beside each ticker — left-aligned rather than
   centered, so a wrapped line reads as a list item and not a broken sentence.
   flex: 1 claims the half of the row the ticker does not, which is what puts
   the text's left edge directly after the ticker; min-width: 0 lets a long
   sentence wrap inside that half rather than widen the row. */
.income-equivalents {
  display: flex;
  flex-direction: column;
  gap: 12px;
  text-align: left;
  flex: 1;
  min-width: 0;
  /* Level with the figure, not the title. The ticker stack runs title, rule,
     figure, and the figure is its last child with no margin under it (see
     .income-ticker .effekt-kpi__value-row), so the stack's bottom edge IS the
     bottom of the number — dropping this column to that edge is what puts
     "Tilsvarer …" on the number it restates. Inheriting the row's flex-start
     put it up beside the heading instead. Bottom rather than centre so a
     sentence that wraps to two lines still ends on the number rather than
     straddling it; the phone rule resets this, where the axis is horizontal. */
  align-self: flex-end;
}
.income-equivalents[hidden] { display: none; }
.income-equivalents__item {
  display: flex;
  align-items: center;
  gap: 10px;
  /* The same line as the "Oppdatert 11:45" timestamp and the "Nok til å dekke"
     line — all three are a sentence explaining the figure beside them, so they
     are one size. At 36px this one was larger than the 27px heading above it,
     which put the caption above the title in the reading order. */
  font-size: var(--fs-kpi-meta);
  color: var(--text-muted);
}
/* The traktor line says what one traktor is and where the price comes from.
   [data-tip]::after is absolute against its host and deliberately sets no
   position of its own, so the span has to supply one. */
.income-equivalents__item span[data-tip] { position: relative; }
/* Sized to the text, not to 45px: these are 24-unit glyphs, and at 45px their
   strokes go to hairlines and the tractor reads as a bicycle sketch. */
.income-equivalents__item svg {
  flex-shrink: 0;
  width: var(--fs-kpi-meta);
  height: var(--fs-kpi-meta);
}
.income-equivalents__item b { color: var(--text); font-weight: 700; }

/* Grunneierinntekter now mirrors Skatteinntekter exactly — ticker left, its
   traktor equivalent filling the half beside it — so the two figures line up
   on the same left edge, and so do the two "tilsvarer" sentences.
   It used to be its own flex row (title left, ticker right); the shared
   .income-box__row inside handles that now, so this must NOT be a flex row
   itself or the ticker would be centred against the row instead of filling it. */

/* Same treatment for the production / effekt cards: a plain-language
   comparison under the figure, so a MW or GWh number means something to a
   reader who does not work with these units. */
.kpi-context {
  display: flex;
  align-items: center;
  /* Left, on the same axis as the heading and figure above it — this is the
     "Oppdatert 11:45 …" and "Nok til å dekke …" line. */
  justify-content: flex-start;
  gap: 7px;
  margin-top: 6px;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--text-muted);
  text-align: left;
}
/* The two bottom context lines — "Nok til å dekke årsforbruket til X
   husstander" and the effekt card's "Oppdatert ..." timestamp — roughly doubled
   from the shared 11.5px. They sit side by side in the same row, so they are
   sized together: at 11.5px the timestamp read as fine print next to its
   neighbour rather than as the same kind of line. */
#ytd-context,
#effekt-updated {
  font-size: var(--fs-kpi-meta);
  gap: 10px;
  /* Outdented by exactly its own glyph plus the gap, so the TEXT starts on the
     card's --kpi-inset axis — the same left edge as the title above it and the
     figure between them. Left in the flow, the glyph pushed the sentence 32px
     right of everything else on the card and the three lines read as a stagger
     rather than a stack. The glyph now hangs into the inset instead, which is
     what that 34px is there for. */
  margin-left: -28px;   /* the 18px glyph below plus this rule's 10px gap */
}
/* 18px rather than the line's own --fs-kpi-meta: the outdent above is exactly
   glyph + gap, so a full-size glyph put its left edge 2px inside the card. At
   18px it clears by 6px and still reads at a glance — and the text keeps the
   --kpi-inset axis either way, which is the point of the outdent. */
#ytd-context svg,
#effekt-updated svg { width: 18px; height: 18px; }
/* The effekt card's figure sits in a row with a donut (up to 78px tall) and a
   timestamp beneath; the production card has just the number. Give the two
   blocks the same minimum height so their context lines land level. */
/* Both cards now use .effekt-kpi__body-row for their figure block, so one
   floor keeps their context lines level. */
.effekt-kpi__body-row { min-height: 72px; }

/* Both cards now end in a .kpi-context line — the effekt card's is its
   "Oppdatert ..." timestamp, the YTD card's its household comparison — so the
   padding that used to stand in for the effekt card's in-figure timestamp is
   gone with it. */

.kpi-context svg { flex-shrink: 0; opacity: 0.75; }
.kpi-context b { color: var(--text); font-weight: 700; }

/* Reset all white-text overrides back to dark */
.bidrag-cell--effekt .eyebrow {
  color: var(--text-muted);
}
/* 27px on all four cards in the grid — the income tickers are back in this
   selector, which is where they started. On each of these cards the eyebrow is
   the whole title, and every one fits a line at this size in a half-width
   card: "Nåværende småkraftproduksjon" is the long one and still clears it. */
.bidrag-cell--income .effekt-kpi__eyebrow,
.bidrag-cell--effekt .effekt-kpi__eyebrow,
.bidrag-cell--ytd .effekt-kpi__eyebrow {
  font-size: var(--fs-kpi-title);
}

/* ── Card headings on this slide: full-strength text, no tracking ──────────
   The shared .effekt-kpi__eyebrow is a grey micro-label, which is the right
   treatment at 9px in a dense panel. On these three cards the eyebrow is the
   card's own title, so grey would read as disabled next to the black figure
   it heads, and the residual tracking that suits a 9px label is slack at
   heading size. All three together now: the income labels used to be split
   out to protect their all-caps treatment, and nothing on the slide is
   capitalised any more. */
.bidrag-cell--income .effekt-kpi__eyebrow,
.bidrag-cell--effekt .effekt-kpi__eyebrow,
.bidrag-cell--ytd .effekt-kpi__eyebrow {
  color: var(--text);
  letter-spacing: 0;
  font-weight: 700;
}

.bidrag-cell--income .effekt-kpi__value,
.bidrag-cell--effekt .effekt-kpi__value {
  color: var(--text);
}
.bidrag-cell--income .effekt-kpi__unit,
.bidrag-cell--effekt .effekt-kpi__unit {
  color: var(--text-muted);
}

/* Income figures take the shared --fs-kpi-value like every other card. They
   used to run 1.5x larger (up to 60px), which is what let nine digits fill the
   box and left Skatteinntekter looking like a different species of card from
   Produksjon hittil i år sitting right beneath it.
   The 20px this rule used to set on the unit is gone with the same reasoning:
   it was a fourth size on a slide built from three, and .effekt-kpi__unit's own
   --fs-kpi-meta is what every other card's unit reads at. */
/* Match the effekt/income cards: value in full-strength text, unit muted.
   This card previously darkened the unit too, which made "GWh" read heavier
   than the "MW" and "NOK" beside it. */
.bidrag-cell--ytd .effekt-kpi__value { color: var(--text); }
.bidrag-cell--ytd .ytd-vbar__htick-label {
  color: var(--text-muted);
}

/* KPI box hover. .is-peer-hover is set by script.js on the other card in the
   same chart group: effekt and YTD both drive the production view, so hovering
   either one should light up both — they are one control, not two. The two
   income boxes are NOT such a group: each drives its own stream, so each
   lights up on its own. */
/* Shadow only on hover — no lift. A translateY here used to nudge the card up
   a pixel, which reflows nothing but reads as the whole card shifting under
   the cursor, which is the annoying part; the shadow alone still says
   "hovered" without anything appearing to move. */
.bidrag-cell--effekt:hover,
.bidrag-cell--ytd:hover,
.bidrag-cell--effekt.is-peer-hover,
.bidrag-cell--ytd.is-peer-hover {
  box-shadow: 0 4px 14px rgba(0,0,0,0.12);
  transition: box-shadow 0.15s ease;
}
.income-box:hover,
.income-box.is-peer-hover {
  box-shadow: 0 4px 14px rgba(0,0,0,0.12);
}
.bidrag-cell--effekt,
.bidrag-cell--ytd {
  transition: box-shadow 0.15s ease;
}

/* Donut hover tooltip */
#donut-tooltip {
  position: absolute;
  background: #fff;
  border: 1px solid rgba(0,0,0,0.12);
  border-radius: 6px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.12);
  padding: 5px 9px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--text);
  pointer-events: none;
  white-space: nowrap;
  display: none;
  z-index: 100;
}
#donut-tooltip strong { display: block; font-size: 12px; color: var(--text); }
#donut-tooltip span   { color: var(--text-muted); }

/* Adjust donut label colors rendered by canvas – done in JS */

/* Centered value rows within the 4 KPI boxes */
.bidrag-cell .effekt-kpi__value-row {
  justify-content: flex-start;
}

/* Effekt + donut side-by-side. space-between, not center: the figure belongs on
   the heading's left axis, and the donut (and the YTD bar opposite it) stays out
   at the right of the card where it already sat. Centred, the pair floated as a
   unit and the number landed ~95px in from the heading above it. */
.effekt-kpi__body-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
}
.effekt-kpi__body-left {
  flex: 0 0 auto;
  text-align: left;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* Row 1→2 spacing */
.bidrag-cell--effekt,
.bidrag-cell--ytd {
  padding-top: clamp(6px, calc(1 * var(--sheet-vh)), 12px);
}

/* Row 2 Col 1: strip effekt-kpi's own border/padding (row-level border handles it) */
.bidrag-cell--effekt .effekt-kpi {
  margin: 0;
  padding-top: 0;
  border-top: none;
}

/* Row 2 Col 2. Left inset from the shared token so this heading starts on the
   same axis as the other three and clears its own (i) dot — at 8px the dot sat
   on top of the "P" in "Produksjon hittil i år". The right stays tighter than
   the effekt card's: the YTD bar needs the width. */
.bidrag-cell--ytd {
  padding-left: var(--kpi-inset);
  padding-right: 8px;
}
.bidrag-cell--ytd .ytd-section {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: 100%;
}
.ytd-hbar-outer {
  /* Twice the 160px it used to take. That width came from the donut on the
     effekt card beside it, so the two figures mirrored each other, but this bar
     is a 160px-long axis carrying three separate marks and they had nowhere to
     stand apart. The two cards still line up where it matters: the row is
     space-between and .effekt-kpi__body-left is flex:0 0 auto, so the GWh
     figure keeps the same left edge as the MW figure however wide this gets.
     `flex: 0 1` and not `0 0`: at the design width the row has just enough for
     320px, and a narrower card should shrink the bar rather than overflow.
     NOT flex:1 (which is what this was before 160px): that let the bar swallow
     every spare pixel, and the figure moved with it.
     The right margin pulls it off the card's edge, where it sat flush and read
     as pinned to the side rather than as part of the row. */
  flex: 0 1 320px;
  width: 320px;
  min-width: 0;
  padding: 0;
  margin-right: 8px;
}

/* ── Horizontal YTD bar ── */
.ytd-hbar {
  width: 100%;
}
/* Each of the bar's three parts carries its own title (see renderYtdBar), so
   each answers to the pointer on its own. The moves below are the map's, in
   miniature: a region there goes one step darker in its own hue and firms its
   seam, which is the vocabulary this page already uses for "the pointer is on
   this". Rings are drawn with an inset box-shadow rather than a border, or the
   10px bar would change height under the cursor. */
.ytd-hbar__track {
  position: relative;
  width: 100%;
  /* Twice the 10px it was. The bar carries three separate marks (the fill's
     edge, the black tick, the track behind both) and at 10px they were a hair
     apart; it is also the row's only drawing, beside a figure set at up to
     40px, so it was reading as a rule under the number rather than as the
     second half of the pair. Radius stays half the height, which is what keeps
     the ends true half-circles. */
  height: 20px;
  border-radius: 10px;
  /* Matches the effekt donut's track (#c8ddf0) so the two figures on this
     row read as one visual system. */
  background: #c8ddf0;
  overflow: visible;
  transition: background 0.15s ease, box-shadow 0.15s ease;
}
/* One step darker, landing on REGION_PALETTE.blue.light (#aacde8, script.js):
   the tone an unshaded region RESTS at on the map. Borrowed rather than mixed
   fresh so the page keeps one blue ramp.
   Only when the pointer is on the track ITSELF: the fill and the tick sit
   inside it and hover propagates to ancestors, so without the :has() guard
   touching either of them would light all three at once and none of the
   three would read as the thing being asked about. */
.ytd-hbar__track:hover:not(:has(.ytd-hbar__fill:hover, .ytd-hbar__tick--mid:hover)) {
  background: #aacde8;
  box-shadow: inset 0 0 0 1px #8fb8db;
}
.ytd-hbar__fill {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  background: #0050a1;   /* same as the donut's filled arc */
  border-radius: 10px;   /* half the track's height, same as the track */
  transition: width 0.5s ease, box-shadow 0.15s ease;
}
/* The fill is already the darkest thing on the row, so it cannot go darker to
   say "you are on me". It lights instead: a ring in --brand-blue-light, the
   palette's step UP from the fill's own #0050a1. */
.ytd-hbar__fill:hover {
  box-shadow: inset 0 0 0 1.5px var(--brand-blue-light);
}
/* The overhang stays 5px each side, and is NOT doubled with the track. It is
   there so the mark reads as a line crossing the bar rather than a notch in
   it, and 5px past a 20px track still does that; doubled, the tick would be
   40px tall in a row whose whole figure block is 72px. */
.ytd-hbar__tick {
  position: absolute;
  top: -5px;
  bottom: -5px;
  width: 2px;
  transform: translateX(-50%);
  transition: width 0.12s ease, box-shadow 0.12s ease;
}
/* Single reference mark: where an average year stood on this date. Its value
   and percent used to sit in a permanent label below the bar; that text is on
   its own hover now (see renderYtdBar) — .ytd-hbar__tick-label is gone with it. */
.ytd-hbar__tick--mid { background: #15263b; }   /* donut's label ink */
/* Sticky: a 2px line is a 2px target, and the pointer fell off it on the way to
   reading the tooltip. An invisible column either side widens the catch to
   ±7px without moving or widening the mark itself. It deliberately sits over
   the track and the fill, which is what makes the tick win inside that band.
   14px and not wider because the whole bar is only 160px (.ytd-hbar-outer):
   every px here is a px the track can no longer be asked about. */
.ytd-hbar__tick--mid::before {
  content: '';
  position: absolute;
  top: -3px;
  bottom: -3px;
  left: 50%;
  width: 14px;
  transform: translateX(-50%);
}
/* Thicker, plus a soft halo in its own ink. Not lighter: this mark is the one
   dark thing on a pale bar and lifting its tone would make it recede exactly
   when it is being asked about. */
.ytd-hbar__tick--mid:hover {
  width: 3px;
  box-shadow: 0 0 0 2px rgba(21, 38, 59, 0.16);
}

/* ── Instant hover hints: data-tip ────────────────────────────────────────
   THE way to put a hover hint on anything that is not a chart. Put the text in
   a data-tip attribute and this draws it; do not write a fourth tooltip box.
   It wears the page's one plate (--tip-*, see :root), which is the same white
   plate with a hair border that Chart.defaults gives every chart tooltip, so
   the hint over the YTD bar and the hint over the Småkraftproduksjon plot
   beside it are the same object.
   Why an attribute rather than the native `title` it replaced: a title waits
   about a second before it appears, and on small marks that reads as the hint
   being broken rather than slow, because the pointer has moved on by the time
   it arrives. :hover is instant. The 80ms is a fade, not a delay, and starts
   at once.
   Two things the host has to supply. It must be POSITIONED, since the tip is
   absolute against it and this rule deliberately does not set `position`
   itself: the YTD fill and tick are already absolute and re-stating it would
   tear them off their own placement. And nothing between it and the top of the
   card may clip, since the tip sits outside the host's box.
   Centred on the host, wrapping inside a max-width so a long sentence stacks
   into a block rather than running out of whatever contains it. */
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  width: max-content;
  max-width: 230px;
  padding: var(--tip-pad);
  border: 1px solid var(--tip-border);
  border-radius: var(--tip-radius);
  background: var(--tip-bg);
  color: var(--tip-ink);
  font-family: inherit;
  /* 12px is Chart.js's own tooltip body size, so the two read alike. The font
     family is the page's rather than Chart.js's fallback stack, which the
     charts cannot help drawing in. */
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  text-align: left;
  white-space: normal;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.08s ease;
}
[data-tip]:hover::after { opacity: 1; }

/* The one local exception, and it is a SUPPRESSION rather than a show: the YTD
   fill and tick sit inside the track, and hover propagates to ancestors, so
   pointing at either of them would open the track's hint underneath its own.
   Same guard the track's hover tone carries, written the other way round so
   the generic show rule above stays generic. */
.ytd-hbar__track[data-tip]:hover:has(.ytd-hbar__fill:hover, .ytd-hbar__tick--mid:hover)::after {
  opacity: 0;
}
.ytd-hbar__legend {
  text-align: center;
  font-size: 9px;
  color: var(--text-muted);
  margin-top: 2px;
}

/* Daglig produksjon. No longer a cell in .bidrag-grid — it is the only child
   of .bidrag-ts-row, its own band under the cards, so it just fills that band.
   The class stays because the rules below (and the in-graph title / export
   menu wiring) hang off it. */
.bidrag-cell--ts {
  flex: 1;
  min-height: 0;
}
.bidrag-cell--ts .sk-ts-section {
  flex: 1;
  border-top: none;
  margin-top: 0;
  padding-top: 0;
  background: none;
}

/* ── Effekt + YTD side-by-side row ── */
.effekt-ytd-row {
  display: flex;
  gap: 12px;
  align-items: stretch;
}
.effekt-kpi {
  flex: 1;
  min-width: 0;
}

/* ── Vertical YTD bar column ── */
.ytd-vert-section {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin: 12px 0 16px;
  padding-top: 14px;
  border-top: 1px solid rgba(0,0,0,0.07);
  width: 88px;
}
.ytd-vert-section > .effekt-kpi__eyebrow {
  margin-bottom: 8px;
}
.ytd-vert-bar-outer {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex: 1;
}
.ytd-vbar {
  position: relative;
  width: 52px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.ytd-vbar__top-label {
  font-size: 8px;
  color: var(--text-muted);
  text-align: left;
  line-height: 1.4;
  margin-bottom: 3px;
}
.ytd-vbar__track {
  position: relative;
  width: 52px;
  border-radius: 7px;
  overflow: visible;
  background: #e5edf5;
}
.ytd-vbar__fill {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: #0050a1;
  border-radius: 0 0 7px 7px;
  transition: height 0.5s ease;
}
.ytd-vbar__fill-top {
  position: absolute;
  left: -10px;
  right: -10px;
  height: 2px;
  background: #0050a1;
}
.ytd-vbar__htick {
  position: absolute;
  left: -6px;
  right: -6px;
  height: 2px;
}
.ytd-vbar__htick--min { background: #93b8d4; }
.ytd-vbar__htick--max { background: #4a7fa5; }
.ytd-vbar__htick-label {
  position: absolute;
  right: calc(100% + 8px);
  font-size: 8px;
  color: var(--text-muted);
  white-space: nowrap;
  transform: translateY(-50%);
  line-height: 1;
}
.ytd-vbar__htick-label--right {
  right: auto;
  left: calc(100% + 8px);
}
.ytd-vert-footer {
  text-align: left;
  margin-top: 8px;
  width: 100%;
}
.ytd-vert-value-row {
  justify-content: center;
  margin-bottom: 2px !important;
}

/* Hero number */
/* .hero-number removed — the income box now uses the shared .effekt-kpi__value
   classes so every KPI number on the slide renders at the same size. */

/* Stat list */
.stat-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0;
  margin-bottom: 40px;
}

.stat-item {
  padding: 18px 0;
  border-top: 1px solid rgba(0,0,0,0.07);
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
}

.stat-item:last-child {
  border-bottom: 1px solid rgba(0,0,0,0.07);
}

.stat-item__label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  padding-top: 3px;
}

.stat-item__value {
  font-family: 'Satoshi', sans-serif;
  font-size: 22px;
  color: var(--text);
  display: block;
  text-align: right;
}

.stat-item__sub {
  font-size: 9px;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  display: block;
  text-align: right;
  margin-top: 2px;
}

/* CTA button */
.btn-outline {
  align-self: flex-start;
  padding: 12px 24px;
  border: 1.5px solid var(--border);
  border-radius: 40px;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}

.btn-outline:hover {
  background: var(--tint-blue);
  border-color: var(--brand-blue);
  color: var(--brand-blue);
}

/* ─────────────────────────────────────────────────────────────
   Responsive
   ───────────────────────────────────────────────────────────── */
/* ─────────────────────────────────────────────────────────────
   Småkraftåret panel (dark header + light scrollable body)
   ───────────────────────────────────────────────────────────── */
.panel--aaret {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.aaret-header {
  background: #fff;
  border-bottom: 1px solid var(--border);
  /* Same padding as .utbygging-header — sized to its own content, same as
     .utbygging-header (no min-height floor): a slide with more header rows
     (e.g. Grunneiere's extra hjemfall toggle) is naturally taller, a slide
     with only a title+region is as short as Utbygging's own header, rather
     than every slide being forced up to match whichever one happens to be
     tallest.
     8/6, down from 22/20: the band was taking about a fifth of the sheet
     before a single figure, and it holds four things (title, description,
     toggle, pie) that each already carry their own spacing. The pie is what
     actually sets this band's depth, so most of the trim is on it (see
     .utbygging-pie-body > svg), with the rest on the title and on the
     description, which is one line now instead of two. Changed on all three
     header classes at once (.bidrag-header, .utbygging-header, this one): a
     title bar that changes depth as you flip through the slides is the thing
     you notice. */
  padding: 8px 28px 6px;
  flex-shrink: 0;
  box-sizing: border-box;
  /* Header pie sits to the right, same row as the title/toggle column —
     see .utbygging-header-left / .utbygging-pie-wrap for the shared shape. */
  display: flex;
  align-items: flex-start;
  gap: 16px;
  /* Must outrank the body content below it (e.g. .kp-body-wrap on Kraftpris,
     which is a later DOM sibling under the same .panel--dark > * { z-index:1 }
     rule and would otherwise win the tie) — the region-panel dropdown lives
     inside this header and needs to float above everything that follows it. */
  position: relative;
  z-index: 10;
}

.aaret-title {
  font-family: 'Satoshi', sans-serif;
  /* 32px. It was sized up by half to 39 from an original 26, which is still
     the largest type on the sheet by a wide margin, and a slide title has
     one line of one or two words to carry, where the figures it sits above
     have to hold a page each. The region chip inside is em-based (0.75em), so
     it comes down with the title and stays in proportion: only its px
     padding and the caret below need scaling to match.
     26px once condensed, tied to --head-condense (syncHeadCondense,
     script.js) so it steps down at the scroll's own pace rather than
     snapping the instant a fixed threshold is crossed. */
  font-size: calc(32px - 6px * var(--head-condense, 0));
  color: var(--text);
  line-height: 1.2;
  /* Anchor for .region-panel, which is position:absolute off the region
     picker button (button.aaret-title__region) living inside this <h2>. */
  position: relative;
}

/* Selected-year indicator next to a slide title (e.g. Utbygging's default 2025 focus) */
/* Muted suffix after a slide title (the active region, or the selected year on
   Utbygging). Separation is spacing alone — no dash. */
/* Region suffix after a slide title. Rendered as a solid chip so it reads as
   the active filter — distinct from the year, which is plain muted text.
   Now the region PICKER itself (a <button>, not a plain <span>) — see
   .region-btn below and addRegionPickerButton in script.js — so this is
   just the shared chip look both share; .region-btn adds the caret and
   interactive states on top. */
/* Sized up by half (0.5em → 0.75em, padding and caret to match): at 13px next
   to a 26px title the active region read as a footnote rather than as the
   filter every figure on the slide is cut by. */
.aaret-title__region {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  vertical-align: middle;
  font-family: inherit;
  font-weight: 600;
  font-size: 0.75em;
  letter-spacing: 0.02em;
  color: var(--brand-blue);
  background: var(--tint-blue);
  border-radius: 8px;
  padding: 10px 20px;
  margin-left: 16px;
}

/* ── Region picker button + dropdown panel ───────────────────────────────
   Replaces the old separate Fylker/Prisområder toggle + <select>: one
   chip, click to open a panel listing both groups together (Fylker header,
   15 fylker; Prisområder header, NO1–NO5) — picking either sets both the
   region AND its geo-mode in one action via setGlobalRegion, same as
   before. addRegionPickerButton (script.js) owns all the DOM wiring; these
   rules are purely visual. */
button.aaret-title__region {
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  transition: background 0.15s, filter 0.15s;
}
button.aaret-title__region:hover {
  background: var(--tint-blue-hover);
}
button.aaret-title__region[aria-expanded="true"] {
  background: var(--tint-blue-hover);
}

.region-btn__caret {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-1px);
  transition: transform 0.15s;
  opacity: 0.9;
}
button.aaret-title__region[aria-expanded="true"] .region-btn__caret {
  transform: rotate(-135deg) translateY(1px);
  opacity: 1;
}

/* "Åpne infografikk", in the Småkraftåret story card's header: a named pill
   where a hamburger and a three-item menu used to be. Softly rounded rather
   than fully round-ended: at 10px it is plainly a button without turning into
   the pill the geo toggles (.geo-btn, border-radius 20px) own, which are a
   different kind of control (those switch what a figure shows, this opens a
   view). Type and tint come from .geo-btn all the same, since the two sit on
   the same slide and should read as one family.
   flex-shrink:0 so a long region badge in the eyebrow beside it squeezes the
   sentence rather than the button's own label. */
.aaret-open-btn {
  flex-shrink: 0;
  margin-left: 12px;
  padding: 8px 18px;
  border: 1.5px solid var(--border);
  border-radius: 10px;
  background: #fff;
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}

.aaret-open-btn:hover {
  background: var(--tint-blue);
  border-color: var(--brand-blue);
  color: var(--brand-blue);
}

/* Grid view for the Småkraftåret KPI cards, applied to the cards container
   itself (not an ancestor) so it still works once skZoomOpen has moved that
   container into the popout modal, away from #slide-aaret. */
.aaret-body--grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 20px;
  align-items: start;
}

.aaret-body--grid .kpi-section-label {
  grid-column: 1 / -1;
  margin: 0 0 4px 0;
}

.aaret-body--grid .zone-comp {
  grid-column: 1 / -1;
}

.aaret-body--grid .kpi-group {
  grid-column: 1 / -1;
}

.aaret-body--grid .kpi-group__label {
  margin-bottom: 12px;
  font-weight: 600;
  color: var(--text);
}

.aaret-body--grid .kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
}

.region-panel {
  position: absolute;
  z-index: 600;
  /* Fallback only — openPanel (script.js) sets top/left from the chip's own
     offset so the panel drops directly beneath it rather than under the title. */
  top: calc(100% + 6px);
  left: 0;
  width: 240px;
  max-height: min(420px, 70vh);
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 28px rgba(0,0,0,0.16);
  padding: 6px;
}
.region-panel[hidden] { display: none; }

.region-panel__group-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  padding: 8px 10px 4px;
}
.region-panel__group-label:first-child { padding-top: 6px; }

.region-panel__option {
  display: block;
  width: 100%;
  text-align: left;
  font-family: inherit;
  font-size: 13px;
  color: var(--text);
  background: none;
  border: none;
  border-radius: 6px;
  padding: 7px 10px;
  cursor: pointer;
}
.region-panel__option:hover { background: var(--tint-blue); }
.region-panel__option--active {
  background: var(--tint-blue);
  color: var(--brand-blue-deep);
  font-weight: 600;
}
.region-panel__option--active:hover { background: var(--tint-blue); }

/* Footnote under the region list: Ctrl-click adds a region to the selection
   instead of replacing it, so several can be read as one figure (NO1 + NO2 +
   NO5). Muted and separated by a rule so it reads as a note about the list
   rather than another thing in it. */
.region-panel__hint {
  margin: 6px 0 0;
  padding: 6px 10px 2px;
  border-top: 1px solid var(--border);
  font-size: 11px;
  line-height: 1.35;
  color: var(--text-muted);
}

/* The year (or year range) the Utbygging bars are filtered to, hanging off the
   end of that slide's title. It was 0.62em of the title's own 39px, at weight
   500 in muted grey, which made the one thing on the header that CHANGES as you
   press the chart the quietest thing on it.
   Now the figure treatment: --fs-kpi-value, the same size every KPI number on
   the dashboard is set at, in full-strength ink at 800. That is the scale's
   point, since a year here is a readout and not a caption. It lands at 40px
   against the title's 39px, so the two read as one line rather than as a title
   with a note after it; --fs-kpi-title (27px) is the step down if that is too
   loud.
   font-size in px, not em, so it stops riding on the title's size: the title is
   a fixed 39px while every other figure on the page tracks sheet height, and
   this now tracks with them. Letter-spacing still comes from the title it sits
   in. */
.aaret-title__year {
  font-size: var(--fs-kpi-value);
  font-weight: 800;
  color: var(--text);
  margin-left: 12px;
}
.aaret-title__year[hidden] {
  display: none;
}

.kpi-section-label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--green-600);
  margin-bottom: 14px;
}

/* How a price-area total was put together — "NO4 = Nordland + Troms + Finnmark
   + 21 % av Trøndelag". Fylke borders and price-area borders do not line up, so
   a zone figure built from county data is a weighted sum; this line is what
   makes that arithmetic checkable instead of asking the reader to trust it.
   Quiet by design: it explains the headline numbers, it is not one of them. */
.zone-comp {
  margin: -8px 0 14px;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text-muted);
}
.zone-comp[hidden] { display: none; }

/* ─────────────────────────────────────────────────────────────
   KPI card grid
   ───────────────────────────────────────────────────────────── */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 0;
}

/* 2-column variant */
.kpi-grid--2 { grid-template-columns: repeat(2, 1fr); }
/* 1-column variant (single full-width card) */
.kpi-grid--1 { grid-template-columns: 1fr; }

/* All 6 cards sit on a flat white surface, with no accent bar and no border. The
   group tint is a STATE, not the resting fill: hovering a card washes it in
   its group colour, and a selected card keeps that same wash (see
   .kpi-card--blue/--orange/--green below). Idle cards are white so the one
   card the pointer or the selection is on is the only coloured thing in the
   grid: a grid of pre-tinted cards had nowhere left to go on hover. */
.kpi-card {
  position: relative;
  background: #fff;
  border: none;
  border-radius: 12px;
  /* Right padding is the glyph's room (56px + its 12px offset + a gap). A <use>
     of a viewBox-less symbol renders 300px square if it is ever given the
     chance, so overflow:hidden is the backstop — see the note that used to sit
     on #slide-aaret .kpi-card, which is where all of this came from.
     The bottom inset is 16px rather than 24px: it pays for the taller type
     below (see .kpi-card__value), so the card keeps the height it had while
     the figure inside it grows. Height with a single-line label — top pad +
     label + its margin + value + its margin + sub + bottom pad:
       68px glyph, 28px figure   16 + 15.4 + 8 + 28 + 5 + 16.8 + 24 = 113.2
       56px glyph, 34px figure   16 + 16.8 + 8 + 34 + 5 + 18.2 + 16 = 114.0 */
  padding: 16px 80px 16px 14px;
  overflow: hidden;
  cursor: pointer;
  transition: background 0.15s;
}

.kpi-card:hover {
  background: var(--tint-blue);
}

/* Card glyph, at the right (see the sprite at the top of index.html). Positioned
   out of the text flow so adding one never reflows a card. It inherits the
   card's accent through currentColor — .kpi-card--blue and friends set the
   colour, one rule below.
   Drawn at 68px and full strength, which used to be Småkraftåret's own override
   while every other slide kept a faint 20px mark in the corner: at that size a
   turbine, a gauge and a banknote are the same grey speck, so the glyph did no
   naming and the cards on Leverandørindustrien and Samfunnsverdi read as
   identical boxes. The card pays for it with right-side padding below, so the
   label, value and sub line all clear the glyph. */
/* 56px, down from 68px. The glyph was the loudest thing on a card whose job
   is the number, and the 12px it gives back widens the text column — which is
   what keeps the larger label (see .kpi-card__label) from wrapping where the
   smaller one did not.
   Dark ink on every variant, not the card's accent: the tint behind the card
   already colour-codes the group, so drawing the glyph in that same colour
   left each grid a wash of one hue. This was #slide-aaret's treatment for
   exactly that reason and it generalises — the per-variant colour rules that
   used to follow (--blue/--orange/--green) are gone. Read as logos, in other
   words, rather than as faint accents.
   Both dimensions are stated in px on purpose: with width:auto (even with
   aspect-ratio) Chrome resolves the width from the SVG's default 300px
   intrinsic size, not from a top/bottom-stretched height, and the glyphs
   render 300px square and spill across the page. overflow:hidden on the card
   is the backstop for that. The card carries the right-side clearance the
   label's own padding used to provide — the value and sub line have to clear
   the glyph too, not just the label. */
.kpi-card__icon {
  position: absolute;
  top: 50%;
  right: 12px;
  transform: translateY(-50%);
  width: 56px;
  height: 56px;
  opacity: 1;
  color: var(--text);
  pointer-events: none;
}
/* A selected card used to fade its glyph to 0.75, which was written for the
   deep brand fill it no longer carries: on the light tint that just made the
   selected card's mark the faintest one in the row. Full strength everywhere,
   same as the base, which is also why #slide-aaret's opacity:1 override is
   gone. */

/* The label shares the top line with the glyph, so it stops short of it. */
.kpi-card__label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  margin-bottom: 8px;
  line-height: 1.4;
  padding-right: 26px;
}

/* 34px, up from 28px. Against a 56px glyph (was 68px) the figure is now the
   biggest thing on the card, which is the whole point of it. The card height
   is held by the reduced bottom padding — the arithmetic is on .kpi-card. */
.kpi-card__value {
  font-family: 'Satoshi', sans-serif;
  font-size: 34px;
  color: var(--green-800);
  line-height: 1;
  margin-bottom: 5px;
}

/* Kraftpris KPI when a fylke spans several price zones: one compact row per
   zone instead of a single figure. The effect is priced per zone, so there is
   no meaningful single number to show. */
.kp-zone-list {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.kp-zone-line {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 17px;
  line-height: 1.15;
}

/* A single zone has room to render at the card's normal figure size; only a
   multi-zone list needs to shrink to fit. */
.kp-zone-list > .kp-zone-line:only-child { font-size: 24px; }
.kp-zone-list > .kp-zone-line:only-child .kp-zone-line__zone { font-size: 12px; }
.kp-zone-list > .kp-zone-line:only-child .kp-zone-line__unit { font-size: 11px; }

.kp-zone-line__zone {
  font-size: 11px;
  font-weight: 700;
  color: var(--text-muted);
  min-width: 26px;
}

.kp-zone-line__val {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.kp-zone-line__unit {
  font-size: 10px;
  font-weight: 600;
  color: var(--text-muted);
}

.kpi-card__unit {
  font-family: 'Satoshi', sans-serif;
  font-size: 15px;
  font-weight: 600;
  color: var(--green-600);
}

.kpi-card__sub {
  font-size: 13px;
  color: var(--text-muted);
  line-height: 1.4;
}

.kpi-card__arrow {
  position: absolute;
  bottom: 10px;
  right: 12px;
  font-size: 13px;
  color: var(--text-muted);
}

/* KPI groups (Utbygging / Leverandør / Samfunnsverdi / Kraftpris) */
.kpi-group {
  margin-bottom: 10px;
}

.kpi-group__label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  margin-bottom: 5px;
}

/* The group tints. Each variant is white at rest and takes its colour on
   hover, and a selectable card holds that same colour while it is the current
   pick, so on a selector row the tinted card IS the selected one. The
   two-class .kpi-card--active selectors beat both the one-class resting rule
   and .kpi-card:hover's blue, which is what keeps a selected orange card
   orange while the pointer is on it. */
.kpi-card--blue   { background: #fff; }
.kpi-card--blue:hover,
.kpi-card--blue.kpi-card--active,
.kpi-card--blue.kpi-card--active:hover { background: var(--tint-blue); }
.kpi-card--orange { background: #fff; }
.kpi-card--orange:hover,
.kpi-card--orange.kpi-card--active,
.kpi-card--orange.kpi-card--active:hover { background: var(--tint-peach); }
.kpi-card--green  { background: #fff; }
.kpi-card--green:hover,
.kpi-card--green.kpi-card--active,
.kpi-card--green.kpi-card--active:hover { background: var(--tint-green); }

/* The infographic is the exception: every card wears its group tint at rest,
   the colour it would take under the pointer in the dashboard. In the grid the
   cards are the whole picture rather than a row to pick from, so the white
   resting fill (which exists to leave the hover somewhere to go) has no job,
   and the four groups reading in four colours at a glance IS the infographic.
   Scoped to .aaret-body--grid, which only the open panel carries and
   skZoomClose strips, so the cards go back to white in the slide.
   Placed after the resting rules so the two-class selectors win the tie, and
   after the :hover ones so the tint holds while the pointer crosses a card. */
.aaret-body--grid .kpi-card--blue,
.aaret-body--grid .kpi-card--blue:hover   { background: var(--tint-blue); }
.aaret-body--grid .kpi-card--orange,
.aaret-body--grid .kpi-card--orange:hover { background: var(--tint-peach); }
.aaret-body--grid .kpi-card--green,
.aaret-body--grid .kpi-card--green:hover  { background: var(--tint-green); }

/* ── Selector card rows (Utbygging, Konsesjon) ─────────────────────────────
   The metric row on Utbygging and the section pair on Konsesjon. Same cards as
   every other slide's KPI grid — same section label above them, same grid, same
   glyph, type scale and sub line — where they used to be a smaller, outlined
   .kpi-pill variant that existed nowhere else on the dashboard and so made
   those two slides read as a different page. Everything below is about the one
   real difference: these are pressable, and they head a chart card in a flex
   column instead of stacking down a scrolling panel.

   Height is their own (flex: 0 0 auto): both parents are flex columns with a
   chart claiming flex:1 below, and without it the row is the only shrinkable
   thing in the column and loses height to the chart. The bottom margin is the
   gap to the view toggles underneath. */
.kpi-grid--select {
  /* Stated here rather than inherited from .kpi-grid so the phone overrides
     below have something class-for-class to beat: the generic collapse rules
     are .kpi-grid on its own, and a selector row must not stack. Utbygging's
     three metrics; Konsesjon's pair takes .kpi-grid--2 with it. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-bottom: 14px;
  flex: 0 0 auto;
}
.kpi-grid--2.kpi-grid--select { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* The section label heading those rows. Same rule, same reason as the grid: on
   both slides it is a flex item in a column whose chart claims flex:1, and a
   text line with the default flex-shrink:1 would give up its height to it. */
#slide-utbygging .kpi-section-label,
#slide-konsesjons .kpi-section-label { flex: 0 0 auto; }

/* Cards are <button>s here, so they need the type reset a <div> never did —
   same rule .verdi-cards carries for its selectable cards. */
.kpi-grid--select .kpi-card {
  font-family: inherit;
  text-align: left;
  display: block;
  width: 100%;
}

/* The selected card takes no fill of its own here: it is the peach wash
   .kpi-card--orange.kpi-card--active gives every orange card, and the white
   cards either side of it are what makes it read as the pick. It used to be a
   deep-orange fill, which is why the white-ink rules that followed (label,
   sub, value, unit, glyph, the inline (i)) are gone: dark ink on peach is
   simply the base treatment, so none of it needs restating. */

/* Label and sub line at 18px on the three card groups that were asked to match
   each other: Utbygging's and Konsesjon's metric selectors, and Privat hjemfall
   in Samfunnsverdi. The size comes from .ny-visning-box__note/__sub, the
   sentence that explains a figure, rather than the 12px/13px every other KPI
   card runs at. Kraftpriseffekt's three readouts are in the set too, and are
   the one group here that is not a selector. Scoped through an id on purpose:
   the small-screen rules further down are class-only and would otherwise lose
   to this one, so each of them repeats its shrink at matching specificity. */
#slide-utbygging .kpi-grid--select .kpi-card__label,
#slide-utbygging .kpi-grid--select .kpi-card__sub,
#slide-konsesjons .kpi-grid--select .kpi-card__label,
#slide-konsesjons .kpi-grid--select .kpi-card__sub,
#verdi-hjemfall-cards .kpi-card__label,
#verdi-hjemfall-cards .kpi-card__sub,
#slide-kraftpris .kpi-grid .kpi-card__label,
#slide-kraftpris .kpi-grid .kpi-card__sub {
  font-size: 18px;
}

/* The bigger-figure/smaller-glyph treatment started here and is now the base
   for every KPI card (see .kpi-card, .kpi-card__icon and .kpi-card__value), so
   this slide needs nothing of its own. */

/* All kpi-card text is dark/black across all slides */
#slide-aaret .kpi-card__label,
#slide-aaret .kpi-card__sub,
#slide-aaret .kpi-card__arrow,
#slide-leverandor .kpi-card__label,
#slide-leverandor .kpi-card__sub,
#slide-verdier .kpi-card__label,
#slide-verdier .kpi-card__sub,
#slide-utbygging .kpi-card__label,
#slide-utbygging .kpi-card__sub,
#slide-konsesjons .kpi-card__label,
#slide-konsesjons .kpi-card__sub,
#slide-grunneiere .kpi-card__label,
#slide-grunneiere .kpi-card__sub { color: var(--text); }
/* Dark ink on every KPI figure, everywhere — the default, rather than six id
   selectors repeating it slide by slide. It used to be the other way round:
   .kpi-card__value/__unit defaulted to brand blue and each slide overrode it,
   which meant a card seen anywhere OUTSIDE a slide fell back to the blue no
   slide actually wants. That is what "Åpne infografikk" is: skZoomOpen moves
   the live column into #sk-zoom, a child of <body>, so the big view came out
   in blue figures on orange and green cards. */
.kpi-card__value { color: var(--text); font-weight: 800; }
.kpi-card__unit  { color: var(--text); }
/* Oppsummering is the exception it always was: its cards carry the brand blue,
   which is that slide's own colour. */
#slide-bidrag .kpi-card__value { color: var(--green-800); }
#slide-bidrag .kpi-card__unit  { color: var(--green-600); }

/* This slide used to take a bigger glyph (76px), the wider clearance to go
   with it, and a dark-ink rule of its own, on the grounds that its cards are
   taller than the rest. All three are gone: dark ink is the base treatment on
   every variant now, and the shared glyph is 56px against a 34px figure —
   keeping 76px here would leave this slide at exactly the
   glyph-louder-than-the-number imbalance that resize exists to fix. The px
   sizing and the 300px-intrinsic-size note that used to live here moved to
   .kpi-card__icon with the dimensions they explain. Only the rule below is
   still this slide's own. */
#slide-aaret .kpi-card__label { padding-right: 0; }

.kpi-map-hint {
  font-size: 12px;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 8px;
}

.kpi-map-hint::before {
  content: '';
  display: inline-block;
  width: 16px;
  height: 1px;
  background: var(--green-600);
  flex-shrink: 0;
}

/* ─────────────────────────────────────────────────────────────
   Utbygging panel
   ───────────────────────────────────────────────────────────── */
.panel--utbygging {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.utbygging-header {
  background: #fff;
  border-bottom: 1px solid var(--border);
  /* See .aaret-header's padding note: all three headers together. */
  padding: 8px 28px 6px;
  flex-shrink: 0;
  display: flex;
  align-items: flex-start;
  gap: 16px;
  /* Same reason as .aaret-header: must outrank the body content below so the
     region-panel dropdown opening out of this header floats over it. */
  position: relative;
  z-index: 10;
}

.utbygging-header-left {
  flex: 1;
  min-width: 0;
}

.utbygging-pie-wrap {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 2px;
}

/* Groups the pie svg + its label/pct so the phone-only mini map (a row
   sibling, see .mini-fylke-map below) can sit beside this whole column
   instead of interleaving with its parts. */
.utbygging-pie-body {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* 78px, down from the 136 renderHeaderPie draws at. The pie is the tallest
   thing in the header and therefore what sets the band's depth, so trimming
   the padding alone would have left the band within a few pixels of where it
   was. Scaled here rather than in the JS because the drawing is sized in its
   own viewBox units (the pulled-out slice needs the overflow:visible the
   inline style gives it), so the SVG scales cleanly and the slice geometry,
   hit testing and the 54px radius all stay as drawn.
   The child combinator matters: .utbygging-pie-wrap also holds the phone-only
   mini fylke map, which is an svg with sizing rules of its own.
   56px condensed (78 - 22), tied to --head-condense rather than a transition:
   a transition timed to a class flip only approximates the scroll it is
   reacting to, where calc() tracks it exactly — see syncHeadCondense. */
.utbygging-pie-body > svg {
  width: calc(78px - 22px * var(--head-condense, 0));
  height: calc(78px - 22px * var(--head-condense, 0));
}

/* Both fade out with --head-condense (opacity) and close their own line
   (max-height) at the same pace, rather than disappearing outright once
   condensed: the region chip beside the title says the same thing in words,
   so nothing is lost once they reach zero. */
.utbygging-pie-label {
  font-size: 10px;
  font-weight: 600;
  color: #555;
  text-align: center;
  /* 3px: this caption and the percentage under it are the other half of the
     pie column's height, and the column is what sets the whole band's depth. */
  margin-top: calc(3px * (1 - var(--head-condense, 0)));
  max-height: calc(1.3em * (1 - var(--head-condense, 0)));
  opacity: calc(1 - var(--head-condense, 0));
  overflow: hidden;
  max-width: 130px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.utbygging-pie-pct {
  font-size: 11px;
  font-weight: 700;
  text-align: center;
  margin-top: calc(1px * (1 - var(--head-condense, 0)));
  max-height: calc(1.3em * (1 - var(--head-condense, 0)));
  opacity: calc(1 - var(--head-condense, 0));
  overflow: hidden;
}

/* Phone-only mini region picker, drawn next to the header pie — see
   renderMiniFylkeMap in script.js. Hidden by default; the narrow-viewport
   media queries below (portrait phone and short landscape) both reveal it
   and switch .utbygging-pie-wrap to a row so it sits beside the pie rather
   than above/below it, each at its own fixed size — see those blocks for
   why fixed. Colors live here at global scope, shared by both viewports. */
.mini-fylke-map { display: none; }

.mini-fylke {
  stroke: #fff;
  stroke-width: 0.6;
  fill: #cfd8e3;
}
.mini-fylke--active {
  fill: #aacde8;
  cursor: pointer;
}
.mini-fylke--active:active { fill: #7ab5d9; }

/* Same per-slide colour as everything else on it — the mini picker is only shown
   on phones, where it was the last blue thing on an orange or green slide. */
.slide[data-group="ring"] .mini-fylke--active  { fill: #f0c78e; }
.slide[data-group="ring"] .mini-fylke--active:active { fill: #e0a457; }
.slide[data-group="verdi"] .mini-fylke--active { fill: #a8d69c; }
.slide[data-group="verdi"] .mini-fylke--active:active { fill: #79bd68; }
/* Selected takes the slide's own dark step, the same way --active takes its
   light one — this was one hardcoded green on every slide, which put a green
   region inside an amber map on Utbygging and Konsesjon and disagreed with the
   header pie's selected slice right beside it. The three values are
   REGION_PALETTE's dark steps in script.js (blue/orange/green), which is what
   the pie and the big map both select in. */
.mini-fylke--selected {
  fill: #0050a1;
  cursor: pointer;
}
.slide[data-group="ring"]  .mini-fylke--selected { fill: #8a4f00; }
.slide[data-group="verdi"] .mini-fylke--selected { fill: #0a7800; }
.mini-fylke--disabled { fill: #e3e7ec; }

.utbygging-body {
  /* Surface, inset and scrolling come from the shared slide-body rule near the
     top of this file. A flex column is this slide's own: the chart card below
     the KPI row has to take the height that is left, and the scroll cue at the
     foot names the utbyggingskostnader comparison under it (data-scroll-hint in
     index.html, syncScrollCues in script.js — .utbygging-body is in
     COL_FIT_SEL alongside .aaret-body). */
  display: flex;
  flex-direction: column;
}

/* Geo mode toggle (in dark header) */
.geo-toggle {
  display: flex;
  gap: 6px;
  margin: 10px 0 6px;
}

/* In a slide header the toggle is a sibling of the pie rather than a row under
   the title (see the notes in index.html): stacked Fylker-over-Prisområder and
   paired with the pie, since the two answer the same question — which geography
   the slide is cut by. stretch so the shorter "Fylker" matches "Prisområder"'s
   width instead of leaving the column ragged; the base margin above is for a
   toggle sitting under a title and would push this one out of line with the
   pie. Phone resets this back to a row — see the media query near the end. */
.bidrag-header > .geo-toggle,
.aaret-header > .geo-toggle,
.utbygging-header > .geo-toggle {
  flex-direction: column;
  align-items: stretch;
  align-self: center;
  justify-content: flex-start;
  gap: 8px;
  margin: 0;
  flex-shrink: 0;
}

/* Sized up by half: at 11px/4px these read as fine print next to the numbers
   they control, and they are the slide's primary controls. */
/* A white pill with a grey outline at rest; the outline goes the moment it is
   hovered or picked. Whether a chip needs that outline is decided by what it
   sits ON, which is why .toggle-btn below answers differently: the geo toggles
   live in the slide header, which is #fff (.bidrag-header, .aaret-header,
   .utbygging-header all set it), so a white chip there has no edge of its own
   and needs one drawn. Once hovered or picked the chip fills with the slide's
   tint, and a tinted pill says "this one" by itself — a grey ring around it
   would only draw a second, weaker boundary inside the first.
   border-color rather than border, so the 1.5px is always there holding the
   chip's size and nothing reflows by 3px between states. `transition: all`
   below already carries the colour, so the ring fades rather than snaps. */
.geo-btn {
  padding: 6px 21px;
  border-radius: 20px;
  border: 1.5px solid var(--border);
  background: #fff;
  font-family: inherit;
  font-size: 16.5px;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  transition: all 0.15s;
}

.geo-btn--active {
  background: var(--tint-blue);
  border-color: transparent;
  color: var(--brand-blue-deep);
}

.geo-btn:hover:not(.geo-btn--active) {
  background: var(--tint-blue);
  border-color: transparent;
}

/* ── Per-slide accents ────────────────────────────────────────────────────────
   Each slide carries data-group (see index.html), matching its tab colour: the
   production slides are blue, utbygging and konsesjon orange, leverandør-
   industrien, samfunnsverdi and kraftpriseffekt green. Blue chrome on a green
   slide read as if it had been lifted from a different part of the analysis.
   Blue is the base above, so only the other two groups need saying. */
.slide[data-group="ring"] .geo-btn--active {
  background: var(--tint-peach);
  color: var(--brand-orange-deep);
}
.slide[data-group="ring"] .geo-btn:hover:not(.geo-btn--active):not(:disabled) { background: var(--tint-peach); }
.slide[data-group="verdi"] .geo-btn--active {
  background: var(--tint-green);
  color: var(--brand-green-deep);
}
.slide[data-group="verdi"] .geo-btn:hover:not(.geo-btn--active):not(:disabled) { background: var(--tint-green); }

/* The region chip in the slide title, and its open state. */
.slide[data-group="ring"] .aaret-title__region {
  color: var(--brand-orange-deep);
  background: var(--tint-peach);
}
.slide[data-group="ring"] button.aaret-title__region:hover { background: var(--tint-peach-hover); }
.slide[data-group="ring"] button.aaret-title__region[aria-expanded="true"] {
  background: var(--brand-orange);
  color: #fff;
}
.slide[data-group="verdi"] .aaret-title__region {
  color: var(--brand-green-deep);
  background: var(--tint-green);
}
.slide[data-group="verdi"] button.aaret-title__region:hover { background: var(--tint-green-hover); }
.slide[data-group="verdi"] button.aaret-title__region[aria-expanded="true"] {
  background: var(--brand-green);
  color: #fff;
}

/* Section headings above a group of cards — drawn from the legacy --green-*
   aliases, which point at the blue palette, so they stayed blue on the
   orange and green slides. */
/* Review marker on a slide header — "fjernes?", i.e. is this slide going away.
   Burgundy on purpose: it must NOT read as part of the palette, because it is a
   question to the team rather than a label for the reader, and every brand
   colour on this page already means something.
   A pill rather than a true circle — "fjernes?" is eight characters, and a
   circle wide enough to hold them would sit taller than the title it hangs off;
   fully rounded ends keep the circular read at this size. The title is
   800-weight and negatively tracked, so the badge resets both.
   Temporary: delete this rule together with the two spans in index.html
   (#slide-aaret and #slide-verdier) once the question has an answer. */
.panel-flag {
  display: inline-block;
  vertical-align: middle;
  margin: 0 0 0 12px;
  padding: 3px 10px 4px;
  border-radius: 999px;
  background: #7d1128;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.25;
  white-space: nowrap;
}

.slide[data-group="ring"] .kpi-section-label { color: var(--brand-orange-deep); }
.slide[data-group="verdi"] .kpi-section-label { color: var(--brand-green-deep); }

/* The list inside the open region panel: hover tint and the current pick. */
.slide[data-group="ring"] .region-panel__option:hover { background: var(--tint-peach); }
.slide[data-group="ring"] .region-panel__option--active,
.slide[data-group="ring"] .region-panel__option--active:hover { background: var(--tint-peach); color: var(--brand-orange-deep); }
.slide[data-group="verdi"] .region-panel__option:hover { background: var(--tint-green); }
.slide[data-group="verdi"] .region-panel__option--active,
.slide[data-group="verdi"] .region-panel__option--active:hover { background: var(--tint-green); color: var(--brand-green-deep); }

/* No price-zone data exists for this slide (aaret, leverandor) — greyed out
   and inert, same treatment as .toggle-btn:disabled. */
/* The two per-group hover rules above carry :not(:disabled) for this rule's
   sake: they are an attribute plus two classes and would otherwise outrank it,
   lighting a parked button up under the pointer. */
.geo-btn:disabled,
.geo-btn:disabled:hover {
  cursor: not-allowed;
  opacity: 0.45;
  background: #fff;
}

/* A slide's own explainer, reachable from the page's top-right corner. One
   per slide, above the header pie, in the header's own padding. Every header
   class (.aaret-header, .bidrag-header, .utbygging-header) is position:relative
   and shares the same 28px side padding, so one offset places it on all of
   them.

   Konsesjon carries the same class: its dot is the one that came first, and it
   is the only one that retargets (syncKonsInfoDots points it at konsesjonsflyten
   in the Konsesjon half and at the selected metric in the other). That is its
   own behaviour, not a different position, so it shares the rule rather than
   keeping a second one beside it. */
.info-dot.slide-info--top {
  top: 10px;
  left: auto;
  right: 12px;
  /* Size and colour come from .info-dot now. This rule used to carry the 24px
     and 14px that made it the odd one out; they are the base for every dot on
     the page instead, so it is left with the one thing that is actually its
     own, which is where it sits. */
}

/* The metric chips belong to the same slide, so they take the same colour rather
   than the generic blue .toggle-btn--active carries elsewhere — matching the
   Strøm chip in the view toggle above them. */
#slide-konsesjons .kons-energi-metric-toggle .toggle-btn--active {
  background: var(--tint-peach);
  color: var(--brand-orange-deep);
}

/* Utbygging's own view/split toggles ("Kumulativ", "Etter størrelse") — same
   reasoning as Konsesjon above, now that the slide is in the orange group. */
#slide-utbygging .toggle-btn--active {
  background: var(--tint-peach);
  color: var(--brand-orange-deep);
}

/* View toggle. Utbygging's only, and the only .toggle-row on the page, so the
   spacing below is safe to state here rather than on a modifier. */
.toggle-row {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
  align-items: center;
  /* 10px on top of the 14px .kpi-grid--select already leaves, so the gap up to
     the metric cards is 24px against the 14px down to the chart (.utbygging-body
     is a flex column, so the two margins add rather than collapse). Uneven on
     purpose: these toggles CONTROL the chart under them and should sit with it,
     while the cards above are a different control answering a different
     question. At an even 14/14 they read as belonging to the cards as much as
     to the chart, which the chips losing their outline only made plainer. */
  margin-top: 10px;
}

.toggle-group {
  display: flex;
  gap: 6px;
  margin-bottom: 14px;
  flex-shrink: 0;
}

/* Same half-again sizing as .geo-btn — these sit in the same role (choose what
   the chart below shows) and looked like fine print at 11px. Same white pill,
   but NO outline, and that is the surface talking rather than an inconsistency:
   every one of these sits on grey, not on white. Utbygging's pair is in
   .utbygging-body and Konsesjon's two are in .kons-body, which takes .slide's
   background — both --tint-grey. A white pill on grey already has an edge, so
   an outline would be a second line around a shape that is visible without it.
   The 1.5px stays, transparent, so these still measure the same as .geo-btn. */
.toggle-btn {
  padding: 7.5px 21px;
  border-radius: 20px;
  border: 1.5px solid transparent;
  background: #fff;
  font-family: inherit;
  font-size: 16.5px;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  transition: all 0.15s;
}

.toggle-btn--active {
  background: var(--tint-blue);
  color: var(--brand-blue-deep);
}

.toggle-btn:hover:not(.toggle-btn--active) {
  background: var(--tint-blue);
}

/* Per-slide hover, the same three-group split .geo-btn carries a few hundred
   lines up (see the ".slide[data-group]" accents block). Only the active state
   had been given the group's colour, so on Utbygging and Konsesjon a chip went
   ORANGE once picked but washed BLUE under the pointer: the hover read as
   belonging to a different slide than the button it was on. Blue stays the
   base, so only the other two groups need saying.
   :not(:disabled) because these outrank .toggle-btn:disabled:hover on
   specificity (a slide attribute plus two classes against three classes), and
   without it a parked chip would light up under the pointer, which is exactly
   what the inert treatment exists to prevent. */
.slide[data-group="ring"] .toggle-btn:hover:not(.toggle-btn--active):not(:disabled) {
  background: var(--tint-peach);
}
.slide[data-group="verdi"] .toggle-btn:hover:not(.toggle-btn--active):not(:disabled) {
  background: var(--tint-green);
}

/* Parked views (see the Konsesjon view toggle in index.html) — same greyed,
   inert treatment as .geo-btn:disabled. */
.toggle-btn:disabled,
.toggle-btn:disabled:hover {
  cursor: not-allowed;
  opacity: 0.4;
  background: #fff;
}


/* The "Vis kraftverk" checkbox that used to live top-right is gone — the search
   box is the only thing that shows or hides markers now, so a second control
   saying the same thing was just competing for the map's corners.
   .map-kv-toggle / .map-kv-toggle label removed with it. */

/* Both map keys share the bottom-right corner and sit SIDE BY SIDE rather than
   stacked: the colourbar on the left, the kraftverk legend on its right. Leaflet
   stacks controls in a corner vertically by default, which put the colourbar
   above the legend and pushed the pair tall enough to reach the kraftverk detail
   card. Order is set explicitly because which control is added first depends on
   whether the choropleth or the markers finish loading first. */
.leaflet-bottom.leaflet-right {
  display: flex;
  flex-direction: row;
  align-items: flex-end;
}

/* The kraftverk card wins over the two map keys, always. Leaflet gives every
   corner z-index 1000 and lets DOM order break the tie, and it appends them
   top-left, top-right, bottom-left, bottom-right: the corner holding the
   legends is the LAST of the four, so it painted over the card in the corner
   above it. That is only visible once the map is big enough for the card to
   grow down into them, which is why it showed up full-screen and not in the
   slide. One step is enough to reverse the tie; both stay under .sk-zoom
   (1200) and .modal. */
.leaflet-top.leaflet-right { z-index: 1001; }
.leaflet-bottom.leaflet-right .choro-legend { order: 1; }
.leaflet-bottom.leaflet-right .map-legend   { order: 2; }

/* Markers hidden: the legend stays put so the corner keeps its shape, but reads
   as inactive. Not pressable — the search box is what brings the markers back. */
/* The kraftverk legend. A landscape card — the two filter groups side by side,
   with the "Alle småkraftverk" switch above them spanning both. Chrome that used
   to be set inline in addKraftverkLegend's onAdd. */
.map-legend {
  /* 25% up on the base sizing below -- see the note on .choro-legend. */
  zoom: 1.25;
  background: #fff;
  padding: 9px 11px 10px;
  border-radius: 6px;
  box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
  font-size: 12px;
  line-height: 1.7;
}
.map-legend__cols {
  display: flex;
  align-items: flex-start;
  gap: 18px;
}
.map-legend__col { flex: none; }
.map-legend__head {
  display: block;
  font-size: 11px;
  color: #666;
  margin-bottom: 4px;
}
/* The filter chips. background-color is set from JS to mark an armed filter,
   so nothing here may set one. */
.map-legend__row {
  cursor: pointer;
  padding: 1px 4px;
  border-radius: 3px;
  white-space: nowrap;
}
.map-legend__glyph {
  vertical-align: middle;
  margin-right: 5px;
}

/* Markers off: the filters have nothing to filter, so the card greys out and
   goes inert. It is a readout now, not a control — the "Alle småkraftverk"
   checkbox that used to head it is gone, and the Pelton button in the map's
   corner does that job. (While the checkbox lived here the rule could not grey
   the whole card: it would have killed pointer events on the one control that
   was the way back.) */
.map-legend--off .map-legend__cols {
  opacity: 0.45;
  filter: grayscale(1);
  pointer-events: none;
}

/* ── Oppsummering: kraftverk circle flicker ──────────────────────── */
@keyframes kv-flicker {
  0%, 80%, 100% { opacity: 0.85; }
  85%           { opacity: 0.25; }
  90%           { opacity: 0.75; }
  93%           { opacity: 0.35; }
  96%           { opacity: 0.85; }
}

#map .leaflet-kraftverkPane-pane path {
  animation: kv-flicker 5s ease-in-out infinite;
}

/* Leaflet stacks same-corner controls in a vertical column by default, one
   under the other. The map title (below) has to sit BESIDE the search box,
   not under it, so this corner turns into a row instead: the only two
   things that ever live in a map's top-left. */
.leaflet-top.leaflet-left {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

/* ── Kraftverk search control ────────────────────────────────────── */
.kv-search {
  display: flex;
  flex-direction: column;
  width: 240px;
}

/* ── Map title: kraftverk or nedbør, whichever is on ─────────────── */
/* Same chrome as .kv-search__box beside it, so the two read as one row of
   controls rather than two different UI languages. Empty/hidden (see
   updateMapTitle, script.js) whenever neither the kraftverk markers nor the
   nedbør overlay is switched on. */
.map-title {
  display: flex;
  align-items: center;
  background: rgba(255, 255, 255, 0.96);
  border-radius: 8px;
  box-shadow: 0 1px 6px rgba(0,0,0,0.16);
  padding: 0 12px;
  height: 38px;
  font-family: 'Satoshi', sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}
.map-title[hidden] { display: none; }

.kv-search__box {
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(255, 255, 255, 0.96);
  border-radius: 8px;
  box-shadow: 0 1px 6px rgba(0,0,0,0.16);
  padding: 0 12px;
}

.kv-search__icon {
  flex-shrink: 0;
  color: #999;
  pointer-events: none;
}

.kv-search__input {
  flex: 1;
  border: none;
  background: transparent;
  padding: 10px 0;
  font-size: 13px;
  font-family: inherit;
  outline: none;
  color: var(--text);
  min-width: 0;
}
.kv-search__input::placeholder { color: #bbb; }

.kv-search__box--active {
  background: rgba(220, 235, 255, 0.97);
  box-shadow: 0 1px 6px rgba(0, 80, 161, 0.2);
}

.kv-search__clear {
  flex-shrink: 0;
  border: none;
  background: transparent;
  color: #888;
  font-size: 14px;
  line-height: 1;
  padding: 2px 2px 2px 4px;
  cursor: pointer;
  display: flex;
  align-items: center;
}
.kv-search__clear:hover { color: #333; }

.kv-search__results {
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.16);
  margin-top: 4px;
  overflow: hidden;
  max-height: 240px;
  overflow-y: auto;
}

.kv-search__item {
  display: flex;
  flex-direction: column;
  padding: 8px 12px;
  cursor: pointer;
  border-bottom: 1px solid #f0f0f0;
  gap: 2px;
}
.kv-search__item:last-child { border-bottom: none; }
.kv-search__item:hover,
.kv-search__item--focused { background: #f0f6ff; }

.kv-search__name {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
}
.kv-search__name strong { color: #0050a1; font-weight: 700; }

.kv-search__meta {
  font-size: 11px;
  color: #999;
}

/* Chart container — white box sitting on the grey body background */
.chart-wrap {
  flex: 1;
  position: relative;
  /* Ceiling as well as floor — see the note on .sk-ts-chart-wrap: an
     unbounded parent column lets the canvas grow without limit. Lifted on the
     slides where the parent IS a definite-height flex column, so the chart can
     reach the bottom of the panel — see the #slide-utbygging/#slide-kraftpris
     rules below. */
  min-height: 180px;
  max-height: calc(52 * var(--sheet-vh));
  background: #fff;
  border-radius: 10px;
  border: 1px solid var(--border);
  padding: 12px 12px 8px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.05);
}

.chart-wrap canvas { cursor: pointer; }

/* Fill the panel on the two slides whose chart IS the panel's main content, so
   its bottom edge lines up with the map beside it instead of stopping short and
   leaving a band of grey. Safe to drop the ceiling here specifically because
   both parents are definite-height flex columns (.utbygging-body already is;
   .kp-body-wrap is made one below), so flex:1 resolves against a real height
   rather than growing unbounded — which is what the ceiling guards against. */
#slide-kraftpris .chart-wrap {
  max-height: none;
}

/* Utbygging's chart no longer owns its column — the utbyggingskostnader block
   is under it — so it takes a definite height instead of flex:1. Same move
   .verdi-graph made when a second graph joined Samfunnsverdi's column: sharing
   a scrolling column's height between two blocks squeezes both to half a card.
   Sized to fill the panel on first paint, so what changed is what is BELOW the
   fold, not what you land on. */
#slide-utbygging .utbygging-body > .chart-wrap {
  flex: 0 0 auto;
  /* Tall enough that the cost block starts just BELOW the fold on a
     design-height window: the cue's job is to say there is more, and it says it
     better over a clean break than over a heading and half a button row that
     are already visible. Shorter windows scale it down and let the block peek,
     which is the honest trade — the chart still has to be readable. */
  height: clamp(240px, calc(58 * var(--sheet-vh)), 580px);
  max-height: none;
}

/* Content-sized and clear of the chart above it. The column scrolls, so this
   block keeps its own height rather than giving it up to the chart, the same
   reason .kons-cases carries flex-shrink:0.
   Written as a descendant selector, and as the whole flex shorthand, to
   outrank `.kons-energi-section { flex: 1 }` further down this file: that rule
   is for Konsesjon, where the section fills a panel that does not scroll, and
   being later it was winning here too. What it left was a 46px box painting
   563px of bars over whatever came after it, and a column whose scrollHeight
   stopped short of its own content, so the foot of the chart could not be
   scrolled to at all. */
.utbygging-body .utbygging-energi {
  flex: 0 0 auto;
  min-height: auto;
  margin-top: 18px;
}

/* ─────────────────────────────────────────────────────────────
   Responsive
   ───────────────────────────────────────────────────────────── */
/* and (not (pointer: fine)): see the note on the first stacked breakpoint,
   above (.scroll-cue) — without it, zooming in on a desktop fell through this
   query the same way an actual narrow window does. */
@media (max-width: 860px) and (not (pointer: fine)) {
  /* Tighter side inset on a narrow window, where 20px either side is a real
     share of the column. Redefined on the token rather than on one element, so
     the KPI grid, the daily-production plot and the Småkraftåret box all narrow
     together and stay flush with each other. The plot used to carry this 14px
     alone, which is how it ended up 6px wider than the cards here at the same
     time as it was 8px narrower than them on the desktop. */
  :root { --slide-body-pad-x: 14px; }

  .split {
    grid-template-columns: 1fr;
    grid-template-rows: auto 360px;
    height: auto;
  }
  /* The desktop sheet is a fixed windowful with .split flexed into what the
     header leaves. Here the page scrolls instead, so the slide is as tall as
     its content and .split takes the height its own rules give it. */
  .slide { height: auto; }
  .slide > .split { flex: none; }

  /* Here the scroller is the document itself, so placeFooter puts the line at
     the end of <body> instead of inside a column: same line, one step more
     room around it, and the slide's own side inset to sit in. */
  .site-footer {
    margin: 10px 14px 0;
    padding: 16px 0 calc(16px + env(safe-area-inset-bottom, 0px));
  }

  .kpi-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  /* The one-line description is a promise the desktop canvas can keep: it is
     a fixed 1920 wide, so the measure never changes and every sentence fits.
     A real window this narrow is not that canvas: the sentences run two to
     four times the width on offer, and holding them to one line would end
     them in an ellipsis after the first few words. They wrap from here down,
     including on phones (the block at 640 repeats this for a short landscape
     window, which can be wider than 860 and so misses this query). */
  .slide-intro {
    white-space: normal;
    overflow: visible;
    min-height: 0;
    /* The base rule's `height` is sized for ONE line (see the note there);
       wrapped onto two to four here, a fixed height would hold the box to
       that single line while overflow:visible let the rest spill into
       whatever sits below it. auto lets the box grow with the text again. */
    height: auto;
  }

  /* Utbygging's and Konsesjon's selector rows keep their own column count
     rather than following the collapse above: the row is one control, and an
     odd card dropped onto a second line reads as a metric that isn't there.
     They pay for staying across with a compact treatment instead — centred,
     the glyph small and in the flow above the label rather than absolute
     beside it, which is the same trade Samfunnsverdi's selector cards make
     (see .kpi-grid.verdi-cards in the 640px block). */
  .kpi-grid.kpi-grid--select {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
  }
  .kpi-grid--2.kpi-grid--select { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kpi-grid--select .kpi-card {
    padding: 8px 6px 10px;
    text-align: center;
  }
  .kpi-grid--select .kpi-card__icon {
    position: static;
    transform: none;
    display: block;
    width: 24px;
    height: 24px;
    margin: 0 auto 3px;
  }
  .kpi-grid--select .kpi-card__label {
    font-size: 9px;
    margin-bottom: 4px;
    padding-right: 0;
  }
  .kpi-grid--select .kpi-card__value { font-size: 17px; }
  .kpi-grid--select .kpi-card__unit  { font-size: 9px; }
  .kpi-grid--select .kpi-card__sub   { font-size: 8.5px; margin-top: 4px; }
  /* Repeats the two shrinks above id-for-id against the 18px rule up at
     #slide-utbygging .kpi-grid--select, which would otherwise win here. */
  #slide-utbygging .kpi-grid--select .kpi-card__label,
  #slide-konsesjons .kpi-grid--select .kpi-card__label { font-size: 9px; }
  #slide-utbygging .kpi-grid--select .kpi-card__sub,
  #slide-konsesjons .kpi-grid--select .kpi-card__sub { font-size: 8.5px; }

  /* One column, and every box sized to its own content: the desktop grid's
     three equal 1fr rows exist to line four boxes up across two columns, which
     stacked in one column they no longer do — and a 1fr row here would hand
     each box a third of a scrolling column's height. `auto` covers all four
     rows (the two income boxes are grid items in their own right — see the
     display:contents on .bidrag-cell--income), since implicit rows are auto
     by default. */
  .bidrag-grid {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    grid-auto-rows: auto;
    column-gap: 0;
  }
  .bidrag-cell--ytd { border-left: none; padding-left: 0; }
  .bidrag-cell--effekt,
  .bidrag-cell--ytd { border-top: none; padding-top: 0; }

  /* The daily-production band gives up its fixed height here, for the same
     reason .split does: this layout scrolls the page normally, and
     .sk-ts-chart-wrap gets an explicit pixel height further down instead of
     flexing against a fixed parent. */
  .bidrag-ts-row {
    height: auto;
    /* Floor, because between 641px and 860px wide the explicit
       .sk-ts-chart-wrap height further down does not apply and the chart
       would fall back to its own 160px minimum. */
    min-height: 320px;
    border-top: none;
    /* Sides come from the token (14px here, see the :root at the top of this
       block); only the bottom is this layout's own. */
    padding: 0 var(--slide-body-pad-x) 16px;
  }
}

/* ─────────────────────────────────────────────────────────────
   Satoshi display weights
   Satoshi is a sans, so the former serif headings/numbers need
   explicit heavy weights to keep their visual impact.
   ───────────────────────────────────────────────────────────── */
.slide-heading,
.aaret-title                  { font-weight: 800; letter-spacing: -0.01em; }
.kpi-card__value,
.stat-item__value,
.fp__val,
.fp__name                     { font-weight: 700; }
.slide-body                   { font-weight: 400; }

/* ─────────────────────────────────────────────────────────────
   Kraftverk card — docked in the map's right-hand white space
   ───────────────────────────────────────────────────────────── */
/* Norway is tall and narrow, so every map here leaves a column of empty white
   to the right of the coastline. The card is parked there (as a Leaflet
   top-right control — see kvDockFor in script.js) instead of floating over the
   marker it describes, which used to hide the very stretch of map you were
   reading about. Hover and a pinned selection share this one dock; only the
   contents change. */
.kv-dock {
  /* The control corner supplies the 10px inset, so this is just the card. */
  width: 240px;
  display: flex;
  /* That white column is still map. Let drags and scroll-zoom pass through
     rather than trapping the pointer on a card that has nothing to click. */
  pointer-events: none;
  /* Never taller than the map: the photo gives up height first (see below), so
     a short sheet crops the picture instead of pushing the card off the map.
     --sheet-vh rather than vh, or this shrinks twice under the auto-fit zoom,
     see the note on --sheet-h. Capped so the card's foot stops above the
     kraftverk legend in the corner below it, not merely inside the map: with
     the card starting as far north as it does there is room for it to grow
     straight down into the legend otherwise. */
  max-height: calc(39 * var(--sheet-vh));
}

/* Pushed south and a little west of the corner Leaflet would put it in. The
   corner itself is where Finnmark sits on a map of Norway, the one stretch of
   the top-right that is NOT empty white, so a card flush to it covered the
   country's northern tip. Dropping it below Finnmark's latitude puts it back
   over open sea.
   Specificity: Leaflet's own `.leaflet-top .leaflet-control` margins are two
   classes deep, so a bare `.kv-dock` would lose to them however late it loads.
   These two numbers are the tuning knobs if it still sits wrong. */
.leaflet-top.leaflet-right .kv-dock {
  /* Tucked up against Norway's eastern border, as far north and west as it goes
     without touching land: 3° north and 1° west of where it used to sit. That
     is what keeps it off the legend in the corner below, since the card used to
     start level with the Arctic Circle and hang down into it.
     Finnmark is still a hard floor. The top edge lands just south of the
     Norway-Finland border at about 69°N, which is as far up as a 240px-wide
     card fits before its top-left corner reaches the coast.

     Both numbers are px-per-degree at the zoom these panels fit Norway at
     (~5.076 in a 719x779 box, i.e. a 8635px world): 1° of longitude is 8635/360
     ≈ 24px, and 1° of latitude is 8635/(360·cos φ), about 67px at the ~69°N
     the card's top edge sits at. Mercator stretches with latitude, so that
     second figure has to be taken at the card's own position, not once and for
     all. Use them if it needs nudging again. */
  margin-top: calc(18.6 * var(--sheet-vh));   /* 3° north, then ½° back south */
  margin-right: 50px;                         /* 1° west */
}

/* No per-slide override here any more: every map runs the full height of the
   sheet below its header, so the offsets above fit all of them. Oppsummering
   needed its own shorter pair while its map row was bounded to half a sheet. */

/* ── The expanded map only ────────────────────────────────────────────────
   Everything above is tuned for the map AT SLIDE SIZE, where the panel is a
   couple of hundred px of white beside Norway and the 18.6 sheet-vh drop is
   what keeps the card off both Finnmark and the legends. Expanded, the same
   offsets leave a third of the column unused above the card while its foot
   runs down into the legends, because the map is suddenly most of the window.
   So the northward move and the taller cap are scoped here rather than
   applied to both, and the slide-size card sits exactly where it always did.
   Level with the top of the map's own button stack, which takes the same 10px
   inset, so the two read as one row of furniture. Its top-left corner can graze
   Finnmark's eastern edge at this size; the card is opaque and, since the
   z-index rule further up, reliably over the legends, which is a fair trade for
   the height.
   The cap is the same BOTTOM edge as the slide-size card (18.6 + 39 = 57.6
   sheet-vh), less the new 10px start, so the foot has not moved while the card
   has gained everything above it.
   --kv-dock-inset is written by alignKvDock (script.js) and lines the card's
   left edge up with the colourbar's, which is a measurement no stylesheet can
   make because the colourbar's position depends on how wide the kraftverk
   legend beside it renders. A custom property rather than an inline
   margin-right, so it is read ONLY by this rule: the moment the map goes back
   into its panel this selector stops matching and the base 50px applies again,
   however stale the measurement left on the element is. Nothing to tear down.
   The 50px fallback also covers a map with no colourbar to measure against. */
#sk-zoom .kv-dock { max-height: calc(57.6 * var(--sheet-vh) - 10px); }
#sk-zoom .leaflet-top.leaflet-right .kv-dock {
  margin-top: 10px;
  margin-right: var(--kv-dock-inset, 50px);
}

.kv-dock[hidden] { display: none; }
/* Text first, picture last, and only the picture is allowed to lose height —
   a cropped photo still reads; a clipped name and effekt figure do not. */
.kv-dock .kv-label__card {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.kv-dock .kv-label__body  { flex-shrink: 0; }
.kv-dock .kv-label__photo { min-height: 0; }

/* Photo strip at the foot of the kraftverk card. Full card width with the
   height following the image's own aspect ratio — `cover` at a fixed height
   cropped the picture, and these are photos worth seeing whole. Card heights
   therefore vary between kraftverk, which is fine. */
.kv-label__photo {
  width: 100%;
  /* Fixed frame: photos vary in aspect ratio, and letting each one set the card
     height made tall images dominate the card (and overflow the map). A 4:3
     window shows landscape shots essentially whole and crops portrait ones
     sensibly, rather than the card resizing per kraftverk. */
  aspect-ratio: 4 / 3;
  background: #f5f5f5;
  overflow: hidden;
  line-height: 0;        /* kills the inline-image descender gap */
}
.kv-label__photo img {
  width: 100%;
  height: 100%;
  display: block;
  /* Fills the 4:3 frame. `cover` rather than `contain` so there are no letterbox
     bars — a slight crop reads better than grey margins in a small card. */
  object-fit: cover;
  object-position: center;
}

/* The photo is a link to its Wikimedia Commons file page — the photographer,
   the licence, the description. Fills the frame so the picture itself is the
   target, with the source named in the corner rather than left implicit.
   pointer-events is the load-bearing line: .kv-dock sets pointer-events:none so
   drags and scroll-zoom pass through the white column the card sits in, and
   without this the link inherits that and cannot be clicked at all. */
.kv-label__credit {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: auto;
}
.kv-label__credit-tag {
  position: absolute;
  right: 0;
  bottom: 0;
  padding: 3px 7px;
  background: rgba(21, 38, 59, 0.62);
  color: #fff;
  font-size: 10px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.01em;
  border-top-left-radius: 5px;
  transition: background 0.15s;
}
.kv-label__credit:hover .kv-label__credit-tag,
.kv-label__credit:focus-visible .kv-label__credit-tag {
  background: var(--brand-blue, #0050a1);
}
.kv-label__credit:focus-visible {
  outline: 2px solid var(--brand-blue, #0050a1);
  outline-offset: -2px;
}

/* The phone card keeps the same 4:3 frame as the floating one, deliberately.
   Letting each photo set its own height there meant the card resized as the
   image arrived — and collapsed to a stub for the kraftverk that have no photo
   at all — so tapping through markers made the page jump around. A fixed frame
   is sized before the image loads, so every card is the same height, and a
   missing photo simply leaves the grey frame empty. */

.kv-label__card {
  background: white;
  border-radius: 6px;
  /* inset shadow replaces border so it doesn't shrink the content box —
     image at width:100% then fills the full 240px card width exactly */
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15), inset 0 0 0 1px #ddd;
  font-size: 12px;
  font-family: 'Satoshi', sans-serif;
  color: #333;
  width: 240px;
  max-width: 240px;
  overflow: hidden;
}

.kv-label__body {
  padding: 12px;
}

.kv-label__name {
  font-weight: 700;
  font-size: 13px;
  color: #0050a1;
  margin-bottom: 4px;
  word-wrap: break-word;
}
/* The name doubles as the link to the plant's Wikipedia article. It was already
   blue and bold, so it reads as a link before it is one — the arrow is what says
   it leads off-site, and the underline appears on hover rather than at rest so
   the card's heading is not permanently ruled.
   pointer-events, as on the photo credit: .kv-dock sets pointer-events:none so
   drags pass through the white column, and a link inside inherits it. */
.kv-label__name-link {
  color: inherit;
  text-decoration: none;
  pointer-events: auto;
}
.kv-label__name-link:hover,
.kv-label__name-link:focus-visible { text-decoration: underline; }
.kv-label__name-ext {
  margin-left: 4px;
  font-size: 0.85em;
  font-weight: 600;
  opacity: 0.65;
}

.kv-label__type {
  font-size: 11px;
  color: #666;
  margin-bottom: 8px;
  font-weight: 500;
}

.kv-label__info {
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-top: 1px solid #eee;
  padding-top: 8px;
}

.kv-label__label {
  font-weight: 600;
  color: #777;
  font-size: 11px;
}

.kv-label__val {
  font-weight: 500;
  color: #0050a1;
  margin-left: 4px;
}

.kv-label__photo-wrap {
  height: 120px;
  overflow: hidden;
}

.kv-label__photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ─────────────────────────────────────────────────────────────
   Chart legend — upper-right overlay shared by the utbygging bar
   chart and the oppsummering (sk-ts) production chart. Position is
   set per host container since each chart's internal chrome differs.
   ───────────────────────────────────────────────────────────── */
/* Legends sit below the plot on every screen size. They used to float over
   the chart as a bordered card, which cost plot area and — on the utbygging
   chart, where the legend is five size classes — overlapped the bars. In flow
   underneath, they cost nothing and read as a caption. */
.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  padding: 8px 2px 0;
  font-size: 12px;
  font-family: 'Satoshi', sans-serif;
  background: none;
  border: none;
  box-shadow: none;
}
/* The wrappers are flex columns so the canvas takes the free space and the
   legend sits under it at its natural height. */
.chart-wrap,
.sk-ts-chart-wrap { display: flex; flex-direction: column; }
.chart-wrap > canvas,
.sk-ts-chart-wrap > canvas,
.sk-ts-plot > canvas { flex: 1; min-height: 0; }

/* The canvas's own box inside the card — see the note in index.html. Same flex
   slot the canvas used to hold directly, so Chart.js still measures a parent
   with a definite height; the point of it is that it excludes the legend, which
   makes its vertical middle the plot's vertical middle. */
.sk-ts-plot {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
}
.chart-legend { flex: 0 0 auto; }
/* Reserves exactly one legend row's height under a chart that has no legend, so
   its plot area ends where a neighbouring chart's does. The height is derived,
   not guessed: .chart-legend is 12px text with 8px of top padding, and a
   .chart-legend-color swatch is what sets the row's own height. */
.chart-legend--spacer {
  visibility: hidden;
  min-height: calc(8px + 1.25 * 12px);
}

/* Step-a-window arrows, on the plot's own left and right edges. This replaces a
   control strip that sat in the flow above the chart: zooming in cost the plot
   the strip's height, so the drawing you zoomed into was smaller than the one
   you zoomed out of, and every press of ‹ or › was a press on a control 30px
   away from the thing it moved.
   Anchored to .sk-ts-plot, whose middle is the drawing's middle. */
.ts-edge {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 20;
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1.5px solid var(--border);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.94);
  color: var(--text-muted);
  cursor: pointer;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.13);
  /* Out of the way until wanted. visibility, not opacity alone: a transparent
     button still swallows the drag-to-zoom gesture underneath it. */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.16s, visibility 0.16s, background 0.15s, color 0.15s, border-color 0.15s;
}
.ts-edge[hidden] { display: none; }
.ts-edge--prev { left: 4px; }
.ts-edge--next { right: 4px; }

.sk-ts-plot:hover .ts-edge,
.ts-edge:focus-visible {
  opacity: 1;
  visibility: visible;
}
.ts-edge:hover {
  border-color: var(--brand-blue);
  color: var(--brand-blue);
  background: #fff;
}

/* Nothing to hover with: a touch screen would never reveal them at all, so
   there they simply stand. */
@media (hover: none) {
  .ts-edge { opacity: 1; visibility: visible; }
}

/* Items flow in a row now (the legend is a wrap container), so the old
   bottom margin that stacked them is gone. */
.chart-legend-item {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 0;
}

.chart-legend-color {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 3px;
  border: 1px solid rgba(0, 0, 0, 0.08);
  flex-shrink: 0;
}

/* Forecast bars: matches the diagonal-stripe canvas pattern used on the chart */
.chart-legend-color--stripe {
  background-color: #fff;
  background-image: repeating-linear-gradient(45deg, #0050a1 0, #0050a1 2px, #fff 2px, #fff 6px);
}

/* Line series (e.g. the 2026 production line): thin bar instead of a swatch box */
.chart-legend-color--line {
  height: 3px;
  border: none;
  border-radius: 2px;
  background-color: #0050a1;
  align-self: center;
}

.chart-legend-label {
  font-weight: 500;
  color: #333;
}
/* The "sammenlignet med" comparison-range note — sits last in the legend row,
   smaller than the year-toggle labels beside it since it's a caption on the
   band, not a control someone reads to switch years. */
.chart-legend-label--small {
  font-size: 11px;
}

/* Year toggle: swaps the chart's foreground line between the current year and
   any past year (see renderYearToggle). Sits where the "2026" label used to
   be static text, so it inherits the legend row's font-size/line-height. */
.sk-year-toggle {
  display: flex;
  gap: 2px;
}
.sk-year-toggle[hidden] { display: none; }

/* Time-resolution toggle, far right of the legend row. Same chips as the year
   toggle beside it — they are the same kind of control (pick one of a few) and
   sit in the same row, so a second visual idiom would only invite reading them
   as different kinds of thing. */
.sk-agg-toggle {
  display: flex;
  gap: 2px;
  margin-left: auto;
}
.sk-agg-toggle[hidden] { display: none; }
.sk-year-toggle__btn {
  font: inherit;
  font-weight: 500;
  color: var(--text-muted, #7c8a9a);
  background: none;
  border: none;
  border-radius: 4px;
  padding: 1px 5px;
  cursor: pointer;
}
.sk-year-toggle__btn:hover { background: rgba(0,80,161,0.08); }
/* Time, on a year with no intraday shards behind it, or on a view too wide to
   draw hours for (see updateAggToggle, which sets the title to say which).
   Faded rather than hidden: a resolution that comes and goes as you zoom or
   change year is harder to trust than one that is visibly unavailable, and
   greyed it also TEACHES the relationship, since it lights up the moment the
   window is narrow enough. */
.sk-year-toggle__btn:disabled {
  opacity: 0.35;
  cursor: default;
}
.sk-year-toggle__btn:disabled:hover { background: none; }
.sk-year-toggle__btn--active {
  color: #0050a1;
  background: rgba(0,80,161,0.12);
}

/* Vertical divider between toggle groups in the same row */
.toggle-divider {
  width: 1px;
  height: 22px;
  background: var(--border);
  flex-shrink: 0;
  margin-bottom: 14px;
  margin: 0 4px 14px;
}


/* .lev-card / .lev-card__title / .lev-act-pie* — the "Verdiskaping etter
   aktivitet" card's own styling — went with the card; see the note in
   index.html above the Grunneiere section. The three activity colours it drew
   its pie in (LEV_ACT_PARTS) live on in Inntekter og sysselsetting's own Verdiskapingseffekt
   pie below, at a smaller size since it now shares a row with two other boxes
   instead of having a full card to itself. */

/* ── Inntekter og sysselsetting: three headline boxes ───────────────────────
   Three columns on desktop, one per box — narrower than the 230px-wide pie
   card these figures used to have to themselves, hence the smaller wedge-only
   pie and plain legend list below (see nvPieSVG/nvPieKeyHTML in script.js)
   rather than leader-line labels: those need the width of a full card, these
   need to sit three across. */
.ny-visning-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* Five named rows, so every box's title, figure, description, pie and foot
     line up with its neighbours' instead of each box stacking independently.
     Without this the pies started at different heights: Skatteinntekter has a
     one-line sub above its pie, Verdiskaping a sentence that wraps to two or
     three, so the two rings sat at different y and the legends under them
     drifted further apart still.
     auto on every row, so a row is as tall as the tallest box needs it. */
  grid-template-rows: auto auto auto auto auto;
  gap: 14px;
  margin-bottom: 20px;
}
@media (max-width: 900px) {
  /* One column: the boxes are stacked and there is nothing left to align
     across, so the shared rows come off and each box sizes to its own
     content again. */
  .ny-visning-grid {
    grid-template-columns: 1fr;
    grid-template-rows: none;
  }
  .ny-visning-box {
    display: flex;
    grid-row: auto;
  }
}
.ny-visning-box {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 18px 20px;
  /* Subgrid over the parent's five rows rather than a flex column: that is what
     makes the pies and their legends line up ACROSS the boxes. A flex column
     cannot, because each box would lay itself out knowing nothing about its
     neighbours. Boxes with fewer children (Grunneierinntekter has no pie) just
     leave their later rows empty.
     justify-items rather than align-items, since this is a grid now. */
  display: grid;
  grid-row: span 5;
  grid-template-rows: subgrid;
  justify-items: center;
  text-align: center;
}
/* A box whose figure cannot honestly be given for the region on screen. Today
   that is Grunneierinntekter with one fylke picked, for the reason spelled out
   on grunneierAvailableFor in script.js, which also swaps the value for a dash.
   Same 0.4 as .kpi-switch[aria-disabled] and .geo-btn:disabled, so "we cannot
   answer this here" looks the same wherever it happens. No cursor change: this
   box is a readout, not a control, so there was never anything to press.
   Dimming the VALUE and the sub line rather than the whole box, so the title
   and its (i) stay at full strength: the explainer is the one thing more worth
   reading when the figure is missing. It has to be done this way round because
   opacity composites the whole subtree, so a 0.4 box cannot have a child at 1
   however the child is styled. */
.ny-visning-box--unavailable .ny-visning-box__value,
.ny-visning-box--unavailable .ny-visning-box__sub,
.ny-visning-box--unavailable .ny-visning-box__figure { opacity: 0.4; }

/* Browsers without subgrid (pre-Chrome 117 / Safari 16) fall back to the flex
   column this used to be: everything still stacks and centres, the pies simply
   do not line up across boxes. Nothing breaks, it just loses the alignment. */
@supports not (grid-template-rows: subgrid) {
  .ny-visning-box {
    display: flex;
    flex-direction: column;
    align-items: center;
  }
}
/* Sysselsettingseffekt has no pie under it, so its box is naturally shorter
   content than the other two — but .ny-visning-grid is a grid, not a flex row,
   so grid's own default (align-items: stretch) already matches every box in
   the row to the tallest one's height with no rule needed here; this box's
   content then sits at the column's default flex-start, same top-aligned start
   as the other two, rather than centring in the leftover space below it. */
  /* Section titles run at --fs-kpi-meta, the size of the front page's
     "Nok til a dekke arsforbruket til ..." line. Set as the TOKEN, never its
     22px: it is redefined at the phone breakpoint and a literal would stop
     following. See the KPI type-scale note at the top of this file. */
.ny-visning-box__title {
  font-family: 'Satoshi', sans-serif;
  font-weight: 800;
  font-size: var(--fs-kpi-meta);
  color: var(--text);
  /* No align-self override: .ny-visning-box already centres its children, and
     this used to opt out of that with flex-start. The (i) dot rides inline, so
     the heading and its dot centre together as one unit. */
  margin-bottom: 10px;
}
.ny-visning-box__value {
  font-family: 'Satoshi', sans-serif;
  font-weight: 800;
  font-size: 28px;
  color: var(--text);
  line-height: 1.15;
}
.ny-visning-box__sub {
  /* 12px -> 18px, +50%. These sit under a --fs-kpi-meta title and a large
     figure, and at 12px they were the smallest type in the column by some way
     — a caption on a card rather than a line anyone reads. Shared with the
     __note sentences below, which are the same kind of text and move with it. */
  font-size: 18px;
  color: var(--text-muted);
  margin-top: 2px;
}
/* The dynamic kjøp figure inside is bold (see renderNyVisning) — everything
   else in the sentence stays plain so the one number that changes with the
   region/year is what the eye lands on. */
.ny-visning-box__note {
  /* 12px -> 18px, in step with __sub above. */
  font-size: 18px;
  color: var(--text-muted);
  /* Centred with everything else in the box. Both sentences used to be pinned
     left while the title, figure and sub above them were centred, which left
     the box reading down two different edges. */
  margin: 6px 0 4px;
}
/* Pinned to the foot of the box. margin-top:auto still does the work in the
   flex fallback; under subgrid it is the shared last row that places it, and
   align-self:end keeps it on that row's bottom edge. */
.ny-visning-box__note--foot {
  margin-top: auto;
  align-self: end;
  padding-top: 10px;
}
/* Grunneierinntekter's "only at national and price-area level" line. Smaller
   than the 18px its siblings run at, deliberately: those sentences CARRY a
   figure and are part of the reading, this one explains why a figure is absent
   and should not compete with the two boxes beside it that do have one. It also
   stays at full strength while the value and the tree above it dim (see
   .ny-visning-box--unavailable), which is the point of it. */
.ny-visning-box__note--caveat {
  font-size: 14px;
  line-height: 1.35;
  /* No width cap. It had one (24ch) to stop a short sentence running the whole
     box, but the sentence is now permanently reserving its own height whether
     it applies or not (see --off below), so every line it is forced to wrap
     onto is height all three boxes carry all the time. At the box's own width
     it takes two lines instead of three, which is about 19px back. */
}
/* The caveat when it does not apply. visibility, not display:none and not the
   hidden attribute: the three boxes share their rows through subgrid, so a
   sentence that leaves the layout takes height off ALL of them and the block
   visibly resizes every time you pick a region. Kept in place, the row is
   always the taller of the two states, which is the height the grid would have
   needed anyway the moment a fylke is selected.
   visibility also takes it out of the accessibility tree, which is right: an
   explanation for a figure that is currently shown should not be read out. */
.ny-visning-box__note--off {
  visibility: hidden;
}
.ny-visning-box__note b {
  color: var(--text);
  font-weight: 700;
}
.ny-visning-box__pie {
  width: 100%;
  margin-top: 10px;
}
/* Grunneierinntekter's stand-in for a pie (see index.html). The artwork is a
   centred tree in a large white frame, only half the source's height, so drawn
   at the 110px the rings beside it are (nvPieSVG) the tree would read at half
   their size. It is scaled up until the drawing itself matches a ring, and the
   frame's white margin is then cropped back to that same 110px box. The crop
   is what lines the tree up with the pies: the box starts where their SVGs do
   (both sit at the top of .ny-visning-box__pie) and centres the drawing on the
   rings' own centre line. It also keeps the scaled-up image from stretching
   the grid row all three boxes share (see the subgrid note above).
   208px is the widest the image can go before the 110px crop starts cutting
   the tree's tip: the drawing spans 24.4% to 75.2% of the source's height.
   Source is white on white with the card, so no plate, border or radius. */
.ny-visning-box__pie--figure {
  height: 110px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.ny-visning-box__figure {
  flex: none;
  width: 208px;
}
.ny-visning-pie-wrap {
  display: flex;
  justify-content: center;
}
.ny-visning-pie-key {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  /* Rows stay name-left / value-right: they are a two-column reading, and
     centring each row would leave the values in a ragged column nobody can
     scan. The LIST is what centres, so two legends of different width sit on
     the same axis as the rings above them. */
  align-items: stretch;
  text-align: left;
}
.ny-visning-pie-key__row {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 11.5px;
}
.ny-visning-pie-key__dot {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  flex-shrink: 0;
  transform: translateY(-1px);
}
.ny-visning-pie-key__name {
  font-weight: 600;
  color: var(--text);
  flex: 1;
}
.ny-visning-pie-key__val {
  color: var(--text-muted);
  white-space: nowrap;
}

.lev-kommer {
  font-size: 13px;
  color: var(--text-muted);
  padding: 28px 0;
  text-align: center;
}

/* Leverandørgrupper — stacked share bar + legend */
.sgroup-track {
  display: flex;
  height: 18px;
  border-radius: 20px;
  overflow: hidden;
  margin-bottom: 14px;
}
.sgroup-seg { height: 100%; }

.sgroup-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
}
.sgroup-leg {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  color: var(--green-800);
}
.sgroup-sq {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  flex-shrink: 0;
}

/* ─────────────────────────────────────────────────────────────
   Samfunnsverdi slide — Verdier
   ───────────────────────────────────────────────────────────── */
/* .verdi-body — the wrapper that once carried both Samfunnsverdi card rows
   (public recipients, then privat hjemfall) over one shared graph slot — is
   gone along with the div it styled: the recipients row left with "Småkraftens
   inntekter" (see the note in index.html above the Grunneiere section), and
   with only privat hjemfall left inside, the wrapper was doing nothing but
   drawing a second .lev-section rule on top of the one below it. Privat
   hjemfall is a direct child of .aaret-body now and
   keeps its own .lev-section spacing and rule without it.

   .lev-section__title is .sk-block-title, near the top of this file. */

.verdi-cards {
  flex-shrink: 0;
}
/* .kpi-grid is three columns; the Grunneiere row's four metrics are not.
   minmax(0, …) so a long label wraps inside its column instead of widening
   it — a single 19-character Norwegian compound has no natural break point,
   and a plain 1fr column never shrinks below it. .verdi-cards--two, the
   staten/kommunen recipient row's own modifier, went with that row — see the
   note in index.html above the Grunneiere section. */
.verdi-cards--four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* Hjemfall's row, since the Grunneierinntekter card moved to Inntekter og
   sysselsetting. */
.verdi-cards--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Cards are buttons now, so they need the type reset a <div> never did. */
.verdi-cards .kpi-card {
  font-family: inherit;
  text-align: left;
  display: block;
  width: 100%;
}

/* Selected card. Nothing of its own: the green wash is the one
   .kpi-card--green.kpi-card--active already carries, against white siblings.
   It used to be a deep-green fill, which is what the white-ink rules here
   (label, sub, value, unit, glyph, the inline (i) further down) were for; on
   the light tint the base dark ink is right, so they are all gone. */

.verdi-cards .kpi-card:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}
/* Inert, so it must not take the hover wash its enabled siblings do. */
.verdi-cards .kpi-card:disabled:hover {
  background: #fff;
}

/* The band naming the second row. Smaller than a section heading — it labels
   three cards, it is not a new chapter. */
.verdi-band {
  display: flex;
  align-items: center;
  gap: 8px;
  /* 14px below, not 8: the band used to be followed by a .kpi-section-label
     that carried its own 14px down to the cards, and that label is gone. 8px
     was the gap to a line of small green type, not to a row of cards, so the
     band takes over the 14px the label was contributing. Safe to set here
     rather than on a modifier — this is the only .verdi-band on the page. */
  margin: 16px 0 14px;
  flex-shrink: 0;
}

/* The band supplies the row's own vertical rhythm, so the heading inside it
   drops the bottom margin it carries everywhere else. */
.verdi-band .kpi-section-label { margin-bottom: 0; }

/* The (i) that opens the explainer. */
/* The (i) beside the Privat hjemfall heading. It opens the three-step hjemfall
   explainer rather than the shared info panel, so it cannot carry .info-dot:
   that class is intercepted on the document in capture phase and would swallow
   its click. It is the same object to a reader, though, so it is built to match
   .info-dot exactly: 24px box, 1px ring, upright glyph, same hover and halo.
   It was the one that looked different on the page, at 18px with a 1.5px ring
   and an italic glyph on a white fill.
   Green is stated outright rather than inherited from the group rule, for the
   same reason: no .info-dot class for that rule to match on. This slide is the
   verdi group, so it is the same green those rules give everything else here. */
.verdi-info-btn {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  border: 1px solid var(--brand-green-deep);
  border-radius: 50%;
  background: transparent;
  color: var(--brand-green-deep);
  font-family: 'Satoshi', sans-serif;
  font-size: 14px;
  font-weight: 700;
  font-style: normal;
  line-height: 1;
  display: grid;
  place-items: center;
  padding: 0;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.15s, box-shadow 0.15s;
}
.verdi-info-btn:hover,
.verdi-info-btn:focus-visible {
  background: var(--brand-green-deep);
  border-color: var(--brand-green-deep);
  color: #fff;
  transform: scale(1.15);
  box-shadow: 0 0 0 3px rgba(10, 132, 0, 0.18);
}

/* The graph slot — the Grunneiere/hjemfall chart, the only one left in it now
   that the fordeling donut it used to share the column with is gone (see the
   note in index.html above the Grunneiere section; .fordeling-donut* went with
   it). Content-sized, not flex:1, so the block is as tall as it needs to be and
   the column scrolls, which is what .aaret-body is built to do (see
   fitScrollColumns and the scroll cue). */
.verdi-graph {
  flex: none;
  display: flex;
  flex-direction: column;
  /* Clear of the metric cards above it. The cards and the plot were butted
     straight together, which read as one block rather than as "pick a metric,
     then read it" — and with the plot carrying its own title inside the canvas
     now, there was nothing between the two to separate them either. */
  margin-top: clamp(12px, calc(2 * var(--sheet-vh)), 22px);
}
.verdi-graph[hidden] {
  display: none;
}

/* ── Modal (privat hjemfall explainer) ── */
.modal[hidden] {
  display: none;
}
/* ── Info dot + panel ────────────────────────────────────────────────────────
   The (i) on a KPI card or a chart. Absolutely placed in the host's top-left
   corner, so every host needs a positioning context — see .info-dot-host. */
/* Outline only at rest. A solid filled disc in the corner of every card is a
   lot of ink for a secondary control — six of them read as a row of bullets
   competing with the figures they annotate. Drawn as a ring with the glyph in
   the same colour, the dot still reads as a button but sits behind the content
   until someone goes looking for it; the fill it used to carry is now the
   hover/selected state (below), which is where the colour actually says
   something. */
/* One information circle for the whole dashboard. Every (i) on the page is this
   object: same box, same 1px ring, same upright glyph, same hover. The size is
   the one the slide's own top-right dot has always used (24px/14px), which is
   now stated here instead of there, so a free-standing dot in a card corner and
   the one in the page corner are the same size rather than 20 against 24.
   Colour is --brand-blue-deep by default and the slide group's own deep shade on
   the other two groups (see the .slide[data-group] rules below), so the (i) on
   an orange page is orange and on a green page green, the way every other piece
   of chrome on those slides already is. */
.info-dot {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 6;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--brand-blue-deep);
  border-radius: 50%;
  background: transparent;
  color: var(--brand-blue-deep);
  font-family: 'Satoshi', sans-serif;
  font-size: 14px;
  font-weight: 700;
  font-style: normal;   /* italic optically pushed the glyph off-centre */
  line-height: 1;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.15s, box-shadow 0.15s;
}
/* Hovered or selected: the dot fills with the colour it was outlined in. That
   is the whole state change — an outline going solid is unmistakable at 20px,
   where the old darken-a-dark-circle was invisible. The slight growth and the
   soft ring stay; the ring is a box-shadow, not an outline, so it does not
   fight the :focus-visible outline below. */
.info-dot:hover,
.info-dot:focus-visible,
.info-dot.is-active {
  background: var(--brand-blue-deep);
  border-color: var(--brand-blue-deep);
  color: #fff;
  transform: scale(1.15);
  box-shadow: 0 0 0 3px rgba(0, 60, 121, 0.18);
}

/* The slide's own colour, matching the tab it sits under and the chips, chart
   accents and section labels already on that slide. Blue is the base above, so
   only the other two groups need saying. The halo repeats each colour as rgba
   because a box-shadow cannot take a bare custom property and an alpha. */
.slide[data-group="ring"] .info-dot {
  border-color: var(--brand-orange-deep);
  color: var(--brand-orange-deep);
}
.slide[data-group="ring"] .info-dot:hover,
.slide[data-group="ring"] .info-dot:focus-visible,
.slide[data-group="ring"] .info-dot.is-active {
  background: var(--brand-orange-deep);
  border-color: var(--brand-orange-deep);
  color: #fff;
  box-shadow: 0 0 0 3px rgba(207, 124, 0, 0.18);
}
.slide[data-group="verdi"] .info-dot {
  border-color: var(--brand-green-deep);
  color: var(--brand-green-deep);
}
.slide[data-group="verdi"] .info-dot:hover,
.slide[data-group="verdi"] .info-dot:focus-visible,
.slide[data-group="verdi"] .info-dot.is-active {
  background: var(--brand-green-deep);
  border-color: var(--brand-green-deep);
  color: #fff;
  box-shadow: 0 0 0 3px rgba(10, 132, 0, 0.18);
}
.info-dot:active {
  background: var(--brand-blue, #0050a1);
  border-color: var(--brand-blue, #0050a1);
  color: #fff;
  transform: scale(1.02);
}
.info-dot:focus-visible { outline: 2px solid #4d6b85; outline-offset: 2px; }
/* display:grid above outranks the UA stylesheet's [hidden] { display: none },
   so a dot hidden from JS would stay on screen unless this says so explicitly —
   same trap .kons-view-toggle[hidden] and .kons-energi-metric-toggle[hidden]
   document. Nothing hides a dot today; this is here so that the day something
   does, it works. */
.info-dot[hidden] { display: none; }

/* Anyone who cannot take the movement still gets the colour and the ring. */
@media (prefers-reduced-motion: reduce) {
  .info-dot,
  .verdi-info-btn { transition: background 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s; }
  .info-dot:hover,
  .info-dot:focus-visible,
  .info-dot.is-active,
  .info-dot:active,
  .verdi-info-btn:hover,
  .verdi-info-btn:focus-visible { transform: none; }
}

/* Hosts. .income-box (not the .income-ticker inside it) so all four cards in
   the Oppsummering grid carry their dot in the same place — the card's own
   top-left corner. Hosted on the ticker it sat inside the figure instead,
   which put two of the four dots somewhere the other two were not. .sk-ts-section is already relative (it anchors
   .day-pop) so it is not repeated here. .bidrag-cell--effekt/--ytd are
   themselves role="button" chart switches — the dot's own click is stopped in
   script.js so pressing it does not also switch the chart underneath. */
.income-box,
.bidrag-cell--effekt,
.bidrag-cell--ytd { position: relative; }

/* Further from the corner than the general default: this one sits over the
   chart itself, sharing the corner with the in-canvas title above the plot, and
   at the base 8px/8px it crowded the frame's edge more than the KPI cards' dots
   do sitting inside their own padded box. */
.info-dot--chart { top: 14px; left: 16px; }

/* Inline variant, for the (i) that now rides at the end of a KPI card's label.
   The absolute corners of these cards are taken — the glyph fills the right
   side and the label, value and sub line stack down the left — so this one sits
   in the text instead of over it, which also means it can never collide with a
   figure however long the label wraps. Smaller than the free-standing dot to
   match the 11px label it sits in. */
.info-dot--inline {
  position: static;
  display: inline-grid;
  margin-left: 0.42em;
  vertical-align: -0.18em;
  /* em, not px, and this is what makes the inline dots consistent rather than
     merely equal. They ride in labels that are 12px on Smakraftaret, 18px on
     Utbygging, Konsesjon, Kraftpris and the hjemfall cards, and 22px on the
     Inntekter og sysselsetting titles. One fixed px size cannot sit correctly
     in all three: at the free-standing 24px it would tower over a 12px label,
     and at 15px it is a speck beside a 22px title. Tied to the text instead,
     every one of them is the same object at the same proportion, which is what
     a reader actually compares. 1.25x the label's own size, which is the
     relationship the old 15px-on-12px pair had and the one this keeps.
     (width resolves against this element's own font-size, hence 1.56 x 0.8.) */
  font-size: 0.8em;
  width: 1.56em;
  height: 1.56em;
  /* The label carries a little letter-spacing; the glyph must not inherit it
     or the "i" is pushed off its own centre. */
  letter-spacing: 0;
}

/* Above Leaflet's own control panes (.leaflet-top / .leaflet-bottom, z-index
   1000 in leaflet.css). At 900 the backdrop covered the map itself but not the
   controls floating over it, so the zoom buttons, the "Søk kraftverk" box, the
   kraftverk legend and the colourbar stayed at full brightness on top of the
   dimmed page while the info panel was open. .sk-zoom hit exactly this and
   documents the same fix; it sits one step higher again so the two keep their
   order. Everything else on the page tops out at 620. */
.modal {
  position: fixed;
  inset: 0;
  z-index: 1100;
  display: grid;
  place-items: center;
  padding: 24px;
}
.modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(21, 38, 59, 0.45);
}
.modal__card {
  position: relative;
  background: #fff;
  border-radius: 14px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.25);
  padding: 20px 24px 24px;
  width: min(900px, 100%);
  max-height: 86vh;
  overflow-y: auto;
}
/* Same size and chrome as the Privat hjemfall explainer (#hjemfall-modal) —
   plain .modal__card, no size override. Only the body needs its own rule, to
   switch between topics and to give an empty section a visible placeholder
   instead of collapsing to nothing. */
/* ...except the technology-comparison topics, which are long-form explainers
   rather than a paragraph of chart notes, and get a panel at 80 % of the
   window instead. Keyed off data-info-size, which open() sets from the topic —
   the panel is shared, so the size has to travel with the content rather than
   be a property of the modal. */
#info-modal[data-info-size="wide"] .modal__card {
  width: 80vw;
  max-width: 80vw;
  height: 80vh;
  max-height: 80vh;
}
/* Two columns at that width: 80vw of unbroken 14px prose on a wide monitor is a
   ~150-character measure, which is past the point where the eye reliably finds
   the next line. */
@media (min-width: 1100px) {
  #info-modal[data-info-size="wide"] .info-modal__body > section:not([hidden]) {
    column-count: 2;
    column-gap: 40px;
  }
  #info-modal[data-info-size="wide"] .info-modal__body > section > p {
    break-inside: avoid;
  }
}

.info-modal__body > section[hidden] { display: none; }

/* Paragraph spacing in the panel. There was no rule for this, so it fell to the
   browser default of 1em, which at this size is 14px: enough to separate two
   lines but not enough to read as the break someone typed into the sheet. A
   blank line in a cell is a deliberate change of subject and should look like
   one. Collapsed margins mean the gap between two paragraphs is 20px, not 40.
   The first and last are flush, so the block still sits tight to the panel's
   own padding rather than opening a gap under the title. */
.info-modal__body > section p {
  margin: 20px 0;
}
.info-modal__body > section > :first-child { margin-top: 0; }
.info-modal__body > section > :last-child  { margin-bottom: 0; }
.info-modal__body > section {
  font-size: 14px;
  line-height: 1.6;
  color: var(--text);
}

/* A glossary of terms inside another box's panel, rather than a dot per term
   (see subgroup_prefix in build_info_descriptions_sections.py: the sheet's own
   sub_id column decides which rows nest where). The konsesjon stages are the
   case: seven words a reader needs to read the figure, which would be seven
   glyphs crowded onto one chart if each had its own.
   Same table as the figures above it, turned to text: .info-table is built for
   numbers, so this undoes the right alignment and the tabular figures and gives
   the term column a width to hold. Terms stay narrow and the sentences wrap in
   what is left, which is what makes the column scannable. */
.info-table--terms {
  font-variant-numeric: normal;
  table-layout: fixed;
}
/* Both classes on the selector, not just the modifier: .info-table's own
   right alignment is declared further down this file at the same specificity,
   so a lone .info-table--terms lost the tie on source order and every sentence
   in the glossary set itself ragged against the right edge of the panel,
   facing the terms across a gap instead of reading down one column. */
.info-table.info-table--terms th,
.info-table.info-table--terms td {
  text-align: left;
  vertical-align: top;
  padding: 12px 14px 14px 0;
}
.info-table.info-table--terms tr:first-child th {
  text-align: left;
}
/* The term column, sized on the HEADER row. table-layout: fixed reads every
   column width off the first row, so the width that used to sit on
   th[scope="row"] one row further down was ignored and the table split itself
   50/50: half the panel held two words and the explanation wrapped in the
   other half. A term is a word or two and the text beside it is a paragraph,
   so the space goes the other way. Still wide enough for "Kartlagt potensial"
   on one line, and capped so a longer term cannot squeeze the sentence. */
.info-table.info-table--terms tr:first-child th:first-child {
  width: 28%;
}
.info-table.info-table--terms th[scope="row"] {
  padding-right: 14px;
}
/* A cell holds paragraphs, one per blank line in the sheet, so the gap between
   them has to exist inside the cell too. Slightly tighter than the 20px the
   prose above the table gets: the row's own padding and the rule under it are
   already saying where one term ends, and 20px on top of that opened a hole
   wide enough to read as a missing row. First and last are flush, or the first
   sentence would sit a paragraph lower than the term it belongs to. */
.info-table.info-table--terms td p {
  margin: 14px 0;
}
.info-table.info-table--terms td p:first-child { margin-top: 0; }
.info-table.info-table--terms td p:last-child  { margin-bottom: 0; }
/* "This text did not come from the sheet." data-info-source="yaml" is written
   by scripts/build_info_descriptions_sections.py onto every section it fills
   from data/info_descriptions.yaml, so anything without it is either written
   by hand here or owned by the Markdown explainers, and editing the sheet will
   not change a word of it. Flagged the way round it is BECAUSE the marker is
   the one thing that script owns: a marker on the other sections would have to
   be added to markup build_info_sections.py regenerates, and the two would
   take it off each other on alternate runs.
   Yellow rather than a border because the point is to be unmissable while
   someone is comparing the panel against the spreadsheet.
   Privat hjemfall is in here too. It is the one explainer that is not a
   data-info section at all but a modal of its own (#hjemfall-modal, opened
   from #hjemfall-info-btn), so the attribute rule above cannot reach it, and
   it is written by hand from top to bottom. Tinting only its body leaves its
   title on white, which is where the info panel's title sits as well. */
.info-modal__body > section:not([data-info-source="yaml"]),
#hjemfall-modal .hjemfall-top {
  background: #fdf8e3;
  border-radius: 8px;
  padding: 12px 14px;
}
/* A figures table inside an information section. The first one is the
   kraftforbruk the besparelse card multiplies against: the card can only show
   one number, and "average of the last four years" is a claim a reader should
   be able to check rather than take, so the years it averages are printed here.
   Tabular numerals and right alignment, because every cell but the first column
   is a quantity and the point of the table is comparing them down a column. */
.info-table {
  width: 100%;
  margin: 14px 0 4px;
  border-collapse: collapse;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.info-table caption {
  caption-side: bottom;
  margin-top: 6px;
  text-align: left;
  font-size: 12px;
  color: var(--text-muted);
}
.info-table th,
.info-table td {
  padding: 4px 8px;
  text-align: right;
  border-bottom: 1px solid var(--border);
}
/* The zone column is a label, not a figure. */
.info-table th[scope="row"] { text-align: left; font-weight: 700; }
.info-table tr:first-child th {
  text-align: right;
  font-weight: 600;
  color: var(--text-muted);
}
.info-table tr:first-child th:first-child { text-align: left; }
/* The totals row is a different kind of number, so it is set apart rather than
   left to read as a sixth price area. */
.info-table__sum th,
.info-table__sum td {
  border-bottom: none;
  border-top: 2px solid var(--border);
  font-weight: 700;
}

/* ── Skatteinntekter: kraftpris + avgifter = total ──────────────────────────
   Three mini plots reading as one equation. The axis labels are HTML siblings
   of each <svg>, never <text> inside it: the svg carries the grey plot
   background, so an in-SVG label always lands inside the plot however the
   viewBox is shifted. Here the grey box is nothing but plot area. */
.skatt-figure {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 14px;
  flex-wrap: wrap;
  margin: 22px 0 4px;
}
.skatt-figure__cell { text-align: center; }
.skatt-figure__title {
  font-weight: 600;
  font-size: 13px;
  line-height: 1.25;
  /* No wider than the plot under it (y label + gap + 130px box), so a long
     title wraps onto two balanced lines instead of widening its cell and
     pushing the three plots apart. */
  max-width: 144px;
  margin: 0 auto 8px;
  text-wrap: balance;
  color: var(--brand-blue, #0050a1);
}
/* Sits on the plot box's own baseline, not the cell's, so the +/= line up with
   the middle of the boxes rather than the captions under them. */
.skatt-figure__op {
  font-size: 26px;
  font-weight: 300;
  color: var(--text-muted, #9ca3af);
  padding-bottom: 40px;
}
.skatt-plot {
  display: flex;
  align-items: center;
  gap: 4px;
}
.skatt-plot__col { display: flex; flex-direction: column; }
.skatt-plot__box {
  display: block;
  width: 130px;
  height: 82px;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  background: #fafafa;
}
/* Rotated bottom-to-top. writing-mode + rotate(180deg) rather than
   `transform: rotate(-90deg)` alone: this way the element's box is already
   tall-and-narrow, so flex reserves the right space for it and the label
   cannot overlap the plot. */
.skatt-plot__ylabel {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: 10px;
  line-height: 1;
  color: var(--text, #15263b);
  /* Line the label up with the box, ignoring the "tid" caption below it. */
  margin-bottom: 16px;
}
.skatt-plot__xlabel {
  font-size: 10px;
  line-height: 1;
  color: var(--text, #15263b);
  text-align: center;
  margin-top: 4px;
}

.modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 8px;
}
.modal__title {
  font-family: 'Satoshi', sans-serif;
  font-size: 19px;
  font-weight: 700;
  color: var(--text);
}
.modal__close {
  width: 32px;
  height: 32px;
  border: none;
  border-radius: 50%;
  background: var(--tint-grey);
  color: var(--text);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.modal__close:hover {
  background: var(--border);
}

/* ─────────────────────────────────────────────────────────────
   Konsesjons slide
   ───────────────────────────────────────────────────────────── */
/* Loud, impossible-to-miss marker that this slide's data is unverified —
   see the note in index.html for why. Diagonal stripes read as "caution"
   at a glance even before the text is legible. */
.kons-test-banner {
  flex-shrink: 0;
  background: repeating-linear-gradient(
    135deg,
    #b23b3b, #b23b3b 14px,
    #8a2424 14px, #8a2424 28px
  );
  color: #fff;
  text-align: center;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 8px 12px;
  text-shadow: 0 1px 2px rgba(0,0,0,0.35);
}

.kons-view-toggle {
  display: flex;
  gap: 6px;
  margin-top: 14px;
}

/* display:flex above outranks the UA stylesheet's [hidden] { display: none },
   so hiding this from JS needs saying explicitly — same as .kons-view[hidden]
   and .kons-energi-metric-toggle[hidden] below. */
.kons-view-toggle[hidden] {
  display: none;
}

.kons-view-toggle .toggle-btn--active {
  background: var(--tint-peach);
  color: var(--brand-orange-deep);
}

/* A flex column so the active view's card can take the leftover height and its
   bottom edge lands on the map's, instead of stopping at the height of the
   drawing and leaving a band of grey underneath. */
.kons-body {
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  /* Bottom inset copied from .panel--map so the card's lower edge lands on the
     same line as the map card's — the shared slide-body inset alone left it
     sitting high of it. */
  padding-bottom: clamp(10px, calc(1.5 * var(--sheet-vh)), 20px);
}

/* ── Case list under a funnel node ───────────────────────────────────────────
   Same white card as the views above it. flex-shrink:0 so it keeps its height and
   .kons-body scrolls instead — the flow above stays the size it was fitted to. */
.kons-cases {
  flex-shrink: 0;
  margin-top: 14px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px 6px;
  /* One height, always — a full list, a three-row list and no selection at all
     all occupy this card. The flow above is drawn to fill whatever is left over
     (see H in renderKonsSankey), so a table that grew and shrank with the
     selection made the whole diagram jump on every click. Fixed here, in the
     one place that decides it, rather than by re-fitting the flow faster.
     Column layout so the scroll area absorbs the variation instead of the card.
     Sized for twelve rows rather than six: at six, most selections needed the
     expand panel to be read at all, which is a lot of ceremony for a list that
     is usually forty rows long. */
  height: 545px;
  display: flex;
  flex-direction: column;
}
.kons-cases[hidden] {
  display: none;
}

.kons-cases__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

.kons-cases__title {
  /* Section titles run at --fs-kpi-meta, the size of the front page's
     "Nok til a dekke arsforbruket til ..." line. Set as the TOKEN, never its
     22px: it is redefined at the phone breakpoint and a literal would stop
     following. See the KPI type-scale note at the top of this file. */
  font-size: var(--fs-kpi-meta);
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--text);
  /* Air under it now that it carries a heading's weight: at 12px it sat close
     to the table and read as a caption on the first row. */
  margin-bottom: 6px;
}

.kons-cases__note,
.kons-cases__empty {
  font-size: 11.5px;
  color: var(--text-muted);
}
.kons-cases__empty {
  padding: 4px 0 8px;
}

/* Long lists scroll inside the card rather than growing it; short ones leave
   the rest of the fixed card empty rather than shrinking it. */
.kons-cases__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: auto;
  /* A visible scrollbar, deliberately. The panel-level cue (a floating pill)
     cannot be used here — inside a table it would sit on top of the rows it is
     pointing at — so the affordance has to be the bar itself. The site default
     is a hairline in --border on a transparent track, which on a white table
     is very nearly nothing. */
  scrollbar-width: auto;
  scrollbar-color: #b6c2d0 #eef1f5;
}
.kons-cases__scroll::-webkit-scrollbar { width: 11px; height: 11px; }
.kons-cases__scroll::-webkit-scrollbar-track {
  background: #eef1f5;
  border-radius: 6px;
}
.kons-cases__scroll::-webkit-scrollbar-thumb {
  background: #b6c2d0;
  border: 2px solid #eef1f5;       /* insets the thumb so the track reads as a track */
  border-radius: 6px;
}
.kons-cases__scroll::-webkit-scrollbar-thumb:hover { background: #93a3b5; }
.kons-cases__scroll::-webkit-scrollbar-corner { background: #eef1f5; }

/* Nothing selected: the card still stands, so the flow above it keeps its size.
   The line is centred in the empty card rather than sitting under a heading —
   there is no heading to sit under. */
.kons-cases--idle {
  align-items: center;
  justify-content: center;
  text-align: center;
}
.kons-cases--idle .kons-cases__empty {
  max-width: 320px;
}

/* table-layout:fixed, so the <colgroup> shares in KONS_CASE_COLS are what
   decide the widths rather than whichever row happens to hold the longest
   string. min-width is the floor those shares stay readable at; under it the
   card scrolls sideways rather than crushing six columns into forty pixels
   each. */
.kons-cases__table {
  width: 100%;
  min-width: 880px;
  table-layout: fixed;
  border-collapse: collapse;
  font-size: 13.5px;
  line-height: 1.4;
}

/* One line per cell, everywhere. This is the whole point of the fixed layout:
   a table where some rows are one line and others three cannot be scanned down
   a column, and the wrapping was costing more rows than the truncation costs
   words. Anything cut carries its full text in a title. */
.kons-cases__table td,
.kons-cases__table th {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The status-side cell links back to the case page on nve.no. Underlined only on
   hover: every linked row would otherwise put a rule under a whole column, and
   the column is a status to read first and a link to follow second. */
.kons-cases__link {
  color: var(--brand-blue);
  text-decoration: none;
}
.kons-cases__link:hover,
.kons-cases__link:focus-visible { text-decoration: underline; }
.kons-cases__ext {
  font-size: 0.85em;
  margin-left: 3px;
  opacity: 0.7;
}

/* Totals row. Sticky to the foot of the scroller, the same way the header is
   sticky to its top, so the sum stays put while the list moves under it. The
   rule above it is a box-shadow rather than a border: with border-collapse the
   cell's own border is dropped once it is positioned. */
.kons-cases__table tfoot td {
  position: sticky;
  bottom: 0;
  background: #fff;
  box-shadow: inset 0 2px 0 var(--border);
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
}
.kons-cases__table th {
  position: sticky;
  top: 0;
  background: #fff;
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  padding: 8px 10px 9px 0;
  /* box-shadow, not border-bottom: a positioned cell in a border-collapse
     table has its own borders dropped, so the rule under the header was not
     being drawn at all. Same fix, and the same weight, as the rule over the
     totals row at the other end of the table. */
  box-shadow: inset 0 -1.5px 0 var(--border);
  cursor: pointer;
  user-select: none;
}
.kons-cases__table th:hover {
  color: var(--text);
}
.kons-cases__table th.is-sorted {
  color: var(--text);
}

/* Fixed-width slot so the header does not shuffle sideways when the arrow moves
   from one column to another. */
.kons-cases__arrow {
  display: inline-block;
  width: 10px;
  margin-left: 3px;
  font-size: 8px;
  color: var(--brand-orange);
}
/* Roomier rows, and the rhythm carried by a stripe rather than by a rule on
   every row: a hairline under all of them plus a 12px row height was what made
   this read as a 1990s data dump. The stripe is faint enough to disappear when
   the hover tint lands on top of it. */
.kons-cases__table td {
  padding: 9px 10px 9px 0;
  color: var(--text);
  vertical-align: middle;
  border-bottom: none;
}
.kons-cases__table tbody tr:nth-child(even) {
  background: #f7f9fb;
}
/* Rounded ends on the stripe, so a row reads as a band rather than as a full
   bleed edge to edge. */
.kons-cases__table tbody td:first-child { border-radius: 5px 0 0 5px; }
.kons-cases__table tbody td:last-child  { border-radius: 0 5px 5px 0; }
.kons-cases__table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.kons-cases__table tbody tr:hover {
  background: var(--tint-peach);
}
/* The row under the cursor is the row you are about to click through to nve.no
   from, so it says so. */
.kons-cases__table tbody tr:hover td { color: var(--text); }

/* Ribbon (transition) tables: each row carries the colour of the stage the case
   actually ended in, set as --row-c from KONS_STAGE_META so the table and the
   flow above it are coloured by one source. Tinted row plus a colour bar on the
   leading cell — the tint alone is too faint to survive the hover state, and it
   should not be the only carrier of the distinction either way. */
.kons-cases__table tr.has-dest {
  background: color-mix(in srgb, var(--row-c) 8%, #fff);
}
.kons-cases__table tr.has-dest td:first-child {
  border-left: 3px solid var(--row-c);
  padding-left: 6px;
}

/* The key is also the control: each swatch sits in a button that shows or hides
   the cases that ended in that stage. Bordered and tinted so it reads as
   pressable rather than as a caption — a plain legend was being ignored.
   Wraps because a transition can have three destinations (Til behandling splits
   into Avslått, Venter på vedtak and Tillatelse) and their names are long. */
.kons-cases__toggles {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 6px;
  flex-shrink: 0;
}

.kons-cases__toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  background: #fff;
  border: 1.5px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.kons-cases__toggle:hover { border-color: var(--text-muted); }

/* Toggled on: carries the destination stage's own colour (--row-c, set inline
   from KONS_STAGE_META), so which outcomes you are looking at is readable from
   the buttons alone. The text is that same hue darkened rather than the raw
   colour, which is too light to read at 12px for stages like Venter på vedtak.
   Off keeps the outline but drains the swatch. */
.kons-cases__toggle.is-on {
  color: color-mix(in srgb, var(--row-c) 65%, #101010);
  background: color-mix(in srgb, var(--row-c) 12%, #fff);
  border-color: var(--row-c);
}
.kons-cases__toggle:not(.is-on) .kons-cases__key {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--border);
}

.kons-cases__key {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex-shrink: 0;
  background: var(--row-c, var(--text-muted));
}

/* The LCOE half fills the panel the same way the flow does: the section takes the
   leftover height, the chart card stretches into it, and the bars share the room
   instead of bunching at the top with grey underneath. min-height:0 lets it
   shrink below its content when the viewport is short, at which point .kons-body
   scrolls — the rows' own min-height stops them collapsing. */
.kons-energi-section {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* display:flex above outranks the UA stylesheet's [hidden] { display: none },
   so hiding this from setKonsSection needs saying explicitly — same trap as
   .kons-view-toggle[hidden]. */
.kons-energi-section[hidden] {
  display: none;
}

#kons-energi-charts.kons-energi-single {
  flex: 1;
  min-height: 0;
  display: flex;
}


.kons-energi-card--large {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.kons-energi-card--large .kons-energi-row {
  flex: 1;
  min-height: 22px;
}

/* All four toggled views (Flytskjema / Trakt / Prikkdiagram / Strøm) share this
   slot, so carding it here keeps them consistent as you switch. Same white-on-
   grey treatment as .chart-wrap, which is the other "figure sitting on the body
   background" in the app. */
.kons-view {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 18px 20px 14px;
  /* Fills the panel (see .kons-body), with the drawing centred in it.
     min-height:0 is what makes the card's height purely the space available: a
     flex item defaults to min-height:auto, so its own content set a floor and
     the card grew past the panel instead of shrinking into it — which is where
     the stray 18-43px of scroll came from. overflow:hidden keeps a rounding
     error from bringing the scrollbar back. renderKonsSankey then sizes the
     drawing from this box (konsSankeyFitBox), so the two agree. */
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.kons-view[hidden] {
  display: none;
}

/* ── The three chart views ──────────────────────────────────────────────────
   Same card as the flow, but a canvas instead of a drawing, so two of
   .kons-view's rules have to give way: justify-content:center is there to
   centre the sankey in leftover space, and a chart has no leftover space —
   it should fill the box. position:relative anchors the (i), the same job it
   does on .kons-beh. */
.kons-view--chart {
  position: relative;
  justify-content: stretch;
  padding-top: 12px;
  /* Definite height, NOT flex:1 — the same reason .kons-beh__chart gives. By
     the time these are laid out .kons-body is already taller than the panel
     (the saksbehandlingstid chart below is 600px on its own), so there is no
     free space for flex-grow to hand out and the card collapsed to its padding:
     25px tall with a canvas drawing into it. The column scrolls, so the room is
     there to ask for. */
  height: 520px;
  flex: 0 0 auto;
}

/* Chart.js needs a real height to draw into. Here the parent IS a
   definite-height flex column (.kons-view is flex:1 inside .kons-body), so
   flex:1 resolves against a real number and the inherited .chart-wrap ceiling
   would only cap it short of the card's own bottom edge — same reasoning as the
   #slide-utbygging rule on .chart-wrap. */
.kons-chart__plot {
  flex: 1;
  min-height: 0;
  max-height: none;
  background: transparent;
  border: 0;
  padding: 0;
}

/* ── The pie beside the chart ────────────────────────────────────────────────
   Used by all THREE chart cards — Status per idag, Historisk and
   saksbehandlingstid — because each is its own reading of a pool (the mapped
   potential, or a saksbehandlingstid region's own vedtak) and the pool should
   not come and go as you press between them. The same side-by-side the flow
   used to make (see #kons-view-sankey) — this is the pool, that is what has
   happened inside it, whichever of the three ways "what happened" is drawn —
   but these are chart cards, so the row is its own element rather than the
   view: .kons-view is a centred column and .kons-view--chart's stretch depends
   on that, and turning the view itself into a row would take the canvas's
   height with it. display:contents on the host so .kons-potensial is a direct
   flex item of the row and its own flex:0 0 132px sizes its WIDTH, exactly as
   it does beside the flow. Each host exists only as a render target for
   renderKonsPotHost. Saksbehandlingstid's own #kons-beh is not itself a flex
   container — this rule's flex:1/min-height:0 do nothing there, harmlessly;
   what does the layout work is display:flex on THIS row for ITS OWN children,
   which needs no flex parent to work, and .kons-beh__chart's fixed 600px
   height, which align-items:stretch then hands the pie beside it. */
.kons-stolper-row {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: row;
  /* stretch, NOT center — the flow's row centres because a sankey is a fixed
     drawing, but the item beside these bubbles is a chart that has to fill the
     card. Centred, .kons-chart__plot took its content height and the bars drew
     into ~200px of a 520px card with white above and below. The bubbles do not
     lose their centring to this: .kons-potensial is a column with
     justify-content:center, so it centres its own items inside the full height,
     and its divider runs the whole way down like a register's edge should. */
  align-items: stretch;
  gap: 18px;
}
.kons-stolper-pot { display: contents; }

/* A region with nothing for the aside to show (no mapped potential — see
   konsPotensialHTML — or, on saksbehandlingstid, a metric whose totals round to
   zero — see konsBehPieHTML) renders no aside at all, so the row is just the
   chart and the gap would be a dangling 18px. Nothing to do — an empty
   display:contents host contributes no flex item, so the gap never appears. */

/* Status per i dag / Historisk, under the metric cards. The gap above comes
   from .kpi-grid--select's own 14px bottom margin, and the gap below matches
   .toggle-group's — so this pair sits off the cards and off the chart by
   exactly what Utbygging's two toggle groups do, which is the whole point:
   these are the same control in the same place on two slides of the same
   group. .kons-view-toggle's own top margin is for a toggle under something
   with no bottom margin of its own, which the selector row is not. */
.kons-body > .kons-view-toggle {
  margin-top: 0;
  margin-bottom: 14px;
  flex: 0 0 auto;
  flex-wrap: wrap;
}

/* ── View 1: flowchart ──────────────────────────────────────── */
.kons-flow {
  display: flex;
  flex-direction: column;
  gap: 0;
  max-width: 900px;
}

.kons-flow__row {
  display: flex;
  align-items: stretch;
  gap: 0;
}

/* Left column of every row lines up under Konsesjonssaker/Ikke søkt's own
   width so the tree reads as a real hierarchy, not a loose stack of rows. */
.kons-flow__spacer { flex: 0 0 200px; }

.kons-box {
  flex: 0 0 200px;
  min-width: 0;
  border-radius: 10px;
  border: 1.5px solid var(--border);
  background: #fff;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.kons-box__title {
  font-weight: 700;
  font-size: 13px;
  margin-bottom: 4px;
}

.kons-box__line {
  font-size: 11px;
  opacity: 0.85;
  line-height: 1.4;
}

.kons-box--grey  { background: var(--tint-grey);  border-color: #c2c2c2; }
.kons-box--amber { background: #fceccb; border-color: var(--brand-orange); color: #7a4c00; }
.kons-box--red   { background: #f6d9d9; border-color: #b23b3b; color: #7a2222; }
.kons-box--teal  { background: #cfeee6; border-color: #0d8a6f; color: #0a4d3f; }
.kons-box--green { background: var(--tint-green); border-color: var(--brand-green); color: #1f5c00; }

.kons-flow__arrow {
  flex-shrink: 0;
  position: relative;
}

.kons-flow__arrow--right {
  width: 34px;
  align-self: center;
  height: 2px;
  background: var(--border);
}
.kons-flow__arrow--right::after {
  content: '';
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  border: 5px solid transparent;
  border-left-color: var(--border);
}

.kons-flow__row--2 .kons-flow__arrow--down,
.kons-flow__row--4 .kons-flow__arrow--down {
  width: 2px;
  height: 20px;
  background: var(--border);
  margin-left: 100px;
  position: relative;
}
.kons-flow__arrow--down::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--border);
}

.kons-flow__note {
  margin-top: 18px;
  font-size: 12px;
  color: var(--text-muted);
}

/* Status per idag's own note sits inside its .chart-wrap flex column, in the
   slot .chart-legend--spacer holds open elsewhere: a caption under the plot,
   not a paragraph 18px below a diagram, so it resets the spacing that rule
   above was written for. */
.kons-chart__plot > .kons-flow__note {
  flex: 0 0 auto;
  margin-top: 0;
  padding-top: 8px;
}

/* The footnote marker itself, sized off the app's own type scale rather than
   a number picked to look right here: --fs-kpi-meta is the size every other
   "sentence that explains the numbers" on the sheet already reads at (see the
   note on .slide-intro), so the asterisk stays legible as a callout instead
   of sitting at the same 12px as the sentence it is marking. line-height:1
   keeps it from inflating the note's own line height to match its font-size. */
.kons-flow__note-mark {
  font-size: var(--fs-kpi-meta);
  line-height: 1;
  font-weight: 700;
  vertical-align: -0.1em;
}

/* Several of these now stack under the flow (reading note, asterisk footnote,
   source). Only the first needs to clear the diagram; the rest close up so they
   read as one block of small print rather than three separate remarks. */
.kons-flow__note + .kons-flow__note {
  margin-top: 4px;
}

/* Source/update cadence sits a notch quieter and slightly apart from the
   footnote it follows. */
.kons-flow__note + .kons-flow__note--source {
  margin-top: 10px;
  font-size: 11px;
}

/* ── View 2: funnel ─────────────────────────────────────────── */
.kons-funnel {
  max-width: 820px;
}

.kons-funnel__row {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 14px;
}

.kons-funnel__label {
  flex: 0 0 130px;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-muted);
}

.kons-funnel__track {
  flex: 1;
  display: flex;
  height: 40px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--tint-grey);
}

.kons-funnel__track--right { justify-content: flex-end; }
.kons-funnel__track--enlarged { height: 46px; }

.kons-funnel__seg {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 600;
  color: #fff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0 8px;
}

.kons-funnel__seg--thin { padding: 0; }

.kons-funnel__seg--grey  { background: #8a8a8a; }
/* Digitalt kartlagt potensial — matches KONS_STAGE_META.potensial in script.js. */
.kons-funnel__seg--slate { background: #4f8fa6; }
.kons-funnel__seg--amber { background: var(--brand-orange); }
.kons-funnel__seg--red   { background: #b23b3b; }
.kons-funnel__seg--teal  { background: #0d8a6f; }
.kons-funnel__seg--blue  { background: var(--brand-blue); }
.kons-funnel__seg--green { background: var(--brand-green); }

.kons-funnel__notes {
  margin: 4px 0 18px 146px;
  font-size: 12px;
  color: var(--text-muted);
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.kons-funnel__caption {
  margin: 8px 0 0 146px;
  font-size: 12px;
  color: var(--text-muted);
}

/* ── View 3: pictogram ──────────────────────────────────────── */
.kons-pictogram {
  max-width: 900px;
}

.kons-pictogram__title {
  font-size: 15px;
  font-weight: 700;
  margin-bottom: 2px;
}

.kons-pictogram__subtitle {
  font-size: 12px;
  color: var(--text-muted);
  margin-bottom: 20px;
}

.kons-pictogram__row {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 20px;
}

.kons-pictogram__label { flex: 0 0 140px; }
.kons-pictogram__label-title { font-size: 13px; font-weight: 700; }
.kons-pictogram__label-sub   { font-size: 11px; color: var(--text-muted); }

.kons-pictogram__grid {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(20, 1fr);
  gap: 4px;
  max-width: 520px;
}

.kons-dot {
  aspect-ratio: 1;
  border-radius: 50%;
  background: #d8d8d8;
}

.kons-pictogram__pct { flex: 0 0 90px; text-align: right; }
.kons-pictogram__pct-num { font-size: 18px; font-weight: 800; }
.kons-pictogram__pct-sub { font-size: 11px; color: var(--text-muted); }

.kons-pictogram__summary {
  margin-top: 8px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  color: var(--text);
}

/* ── View 4: sankey ─────────────────────────────────────────── */
.kons-sankey {
  /* The flow is drawn at a fixed natural size (W=1280 plus 220px labels to the
     right of the last column ≈ 1500px) because the column spacing is tuned to
     keep the labels from colliding — see renderKonsSankey. That is wider than
     the panel, which used to mean scrolling sideways to see the end of the
     flow. fitKonsSankey (script.js) measures natural vs available width and
     puts the ratio here, so the whole flow is visible at once; it never scales
     up past 1. overflow-x stays as a safety net for the case where the fit
     cannot be computed (e.g. the panel is measured at zero width while hidden). */
  zoom: var(--kons-sankey-fit, 1);
  overflow-x: auto;
  /* Explicitly hidden, not left to default: a box with one axis set to auto has
     the other coerced from visible to auto as well, so overflow-x alone was
     enough to put a vertical scrollbar inside the card the moment the scaled
     drawing overshot by a pixel. */
  overflow-y: hidden;
}

.kons-sankey__canvas {
  position: relative;
  /* Just enough for a label that de-collision has pushed above the first bar.
     This used to be 44px, for a column-0 label that sat ABOVE its node; every
     label hangs beside its bar now, and the headroom was charged straight to the
     drawing's own height (see the chrome term in renderKonsSankey's H). */
  margin-top: 14px;
}

.kons-sankey__svg {
  position: absolute;
  top: -50px;
  left: 0;
  overflow: visible;
}

.kons-sankey__node {
  transition: opacity 0.15s, stroke-width 0.15s;
  cursor: pointer;
}
.kons-sankey__node:hover {
  opacity: 0.85;
}
.kons-sankey__node--active {
  opacity: 1;
}

/* Ribbons select the stage they flow into, so they get the pointer too — often
   the easiest target on the diagram, since a small node's box can be a few
   pixels tall while its ribbon spans a third of the canvas. */
.kons-sankey__ribbon {
  transition: opacity 0.15s;
  cursor: pointer;
}
.kons-sankey__ribbon:hover {
  opacity: 0.55 !important;
}
.kons-sankey__ribbon--active {
  opacity: 0.55 !important;
}

/* A row: the two text lines in a column, then the step's glyph to their right.
   Both children shrink to their own content, so the rest of the fixed 220px stays
   empty — which is what keeps the ribbons underneath reachable (see the
   pointer-events note below). */
.kons-sankey__label {
  position: absolute;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 7px;
  width: 210px;
  cursor: pointer;
  border-radius: 6px;
  padding: 2px 4px;
  margin: -2px -4px;
  /* The label box is a fixed 220px and overlaps the ribbons to its right, so it
     was swallowing hovers and clicks meant for them — a grey plate appeared over
     empty space and the ribbon underneath could not be selected at all. Only the
     text and the glyph take pointer events (see below); the rest of the box lets
     them through. */
  pointer-events: none;
}

/* The words themselves are still a target — useful for the small nodes, whose own
   box is only a few pixels tall. */
.kons-sankey__label-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  pointer-events: auto;
}
.kons-sankey__label-title,
.kons-sankey__label-sub {
  pointer-events: auto;
}

/* Column 0 is the source: its label sits to the LEFT of the bar, so the row runs
   the other way and the text is ranged right, hard against the bar it names. */
.kons-sankey__label--left {
  flex-direction: row-reverse;
  text-align: right;
  justify-content: flex-start;
}

/* Labels for the middle columns sit in the ribbon channel rather than beside an
   empty margin — that is what lets the bars be thin and the heights honest. Over
   a ribbon the text needs its own ground to stay readable, so the words (not the
   whole 210px box, which must keep letting clicks through) carry a soft white
   backing. */
.kons-sankey__label-text {
  background: rgba(255, 255, 255, 0.92);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.92);
  border-radius: 3px;
}

/* Ties a label back to its bar when de-collision has pushed it off centre. Only
   drawn for labels that actually moved (see declutter in renderKonsSankey) —
   one under every label would read as a second set of ribbons. */
.kons-sankey__leader {
  fill: none;
  stroke: #b6c2d0;
  stroke-width: 1;
  pointer-events: none;
}

/* The press target for a bar — a band that tiles its column, so the nearest
   stage wins wherever you click and a one-pixel bar is as easy to hit as a tall
   one (see svgHits in renderKonsSankey). Invisible until the pointer is over it,
   then a faint wash shows which stage a press would land on — the confirmation
   matters most for exactly the bars that are too small to aim at. */
.kons-sankey__hit {
  cursor: pointer;
  fill: transparent;
  transition: fill 0.12s;
}
.kons-sankey__hit:hover { fill: rgba(21, 38, 59, 0.07); }

/* A stage the region has no cases at draws no bar (see renderKonsSankey), so its
   label is all that is left of it — muted, to read as "nothing got this far"
   rather than as a figure that failed to load. */
.kons-sankey__label--empty { opacity: 0.45; }

/* The step's glyph, in that stage's colour (set inline from KONS_STAGE_META).
   flex:none so a long stage name never squeezes it out of shape. */
.kons-sankey__label-icon {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  opacity: 0.85;
  pointer-events: auto;
}

/* Hover reads on the text rather than as a plate behind it: the plate was the
   thing that looked like a stray box floating over the diagram. */
.kons-sankey__label:hover .kons-sankey__label-title {
  color: var(--brand-blue-deep);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
/* Selection is shown by the node box itself (a dark outline) — the label used to
   also take a grey backing plate, which read as a second, stray highlight
   floating to the right of the thing you actually picked. The title darkens
   instead, which ties the label to its node without drawing a box. */
.kons-sankey__label--active .kons-sankey__label-title {
  color: var(--brand-blue-deep);
}

/* Bottom-aligned, i.e. hugging the node it sits above. In the cross axis now the
   label is a row — as justify-content it would have pushed the text to the right
   of the box instead. */
.kons-sankey__label--above {
  align-items: flex-end;
}

.kons-sankey__label-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
}

.kons-sankey__label-sub {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 2px;
}

/* The card the circles replace the funnel in. isPhone() picks that renderer, so
   these are max-width:640px ONLY — a short landscape window still gets the
   desktop flow and still needs the 510px floor and the row it is laid out in.
   and (not (pointer: fine)): see the note on the first stacked breakpoint,
   above .scroll-cue — keeps a zoomed-in desktop out of isPhone()'s own query. */
@media (max-width: 640px) and (not (pointer: fine)) {
  #kons-view-sankey {
    /* No floor: five circles and a box are as tall as they are, where the
       five-column funnel needed half a screen guaranteed to it. */
    min-height: 0;
    align-items: flex-start;
    gap: 12px;
  }
  /* Stack the summary above the plot. Keeping the pie beside Chart.js leaves
     too little width for axis labels and bars on a phone. */
  .kons-stolper-row {
    flex-direction: column;
    gap: 12px;
  }
  .kons-stolper-row .kons-potensial {
    flex: 0 0 auto;
    min-height: 0;
    width: 100%;
    display: grid;
    grid-template-columns: 88px minmax(0, 1fr);
    align-items: center;
    gap: 12px;
    padding: 0 0 10px;
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  .kons-stolper-row .kons-pie {
    width: 88px;
  }
  .kons-stolper-row .kons-pie-key {
    min-width: 0;
    gap: 8px;
  }
  .kons-stolper-row .kons-chart__plot,
  .kons-beh__chart {
    width: 100%;
    min-width: 0;
  }
  .kons-view--chart { height: 460px; }
  /* Two classes, and that is the whole point of this rule: .chart-wrap.kons-beh__chart
     up at the top of the file is also two, so a single-class .kons-beh__chart here
     never won and this chart kept the desktop's `height: 600px` AND, far worse,
     its `flex: 1`.
     Worse because of what this block does two rules above: it turns
     .kons-stolper-row from a row into a column. On a row, flex-grow governs
     WIDTH and the height declaration stands, which is what that rule's own
     comment explains. On a column, flex-grow governs HEIGHT, so `height: 600px`
     stopped binding anything and the chart's height became whatever the flex
     algorithm handed it. Its parent has no definite height either, so there was
     nothing at the end of that chain to measure against, and a Chart.js canvas
     with maintainAspectRatio:false grows to fill whatever it is given: the
     canvas grew, the row grew with it, flex-grow handed the growth back, and
     the card ran away down the page a little further on every resize. Measured
     here at 3 674px and still climbing after six scrolls.
     flex: 0 0 auto puts the height declaration back in charge. */
  .chart-wrap.kons-beh__chart {
    flex: 0 0 auto;
    height: 500px;
  }
  .kons-potensial {
    flex: 0 0 88px;
    gap: 12px;
    padding-right: 10px;
  }
  .kons-pie { max-width: 84px; }
  .kons-pie-key { gap: 9px; }
  .kons-pie-key__name { font-size: 10px; }
  .kons-pie-key__val  { font-size: 12.5px; padding-left: 12px; }
  .kons-pie-key__sub  { font-size: 9.5px; padding-left: 12px; }
}

/* ── Phone variant: one circle per stage ────────────────────────────────
   renderKonsFlowPhone's counterpart to .kons-sankey above. The transposed
   sankey that used to live here (.kons-sankey-v, with a numbered legend under
   a bar chart) is gone: same reasoning as in that function's own comment.
   These circles are the potensial bubbles' language reused — area-scaled
   discs, each holding its own number, named directly underneath — so the two
   halves of this card now read as one drawing rather than two conventions. */
.kons-circles {
  display: flex;
  flex-direction: column;
  /* Room for the export menu and, on the desktop-sized card, the info dot —
     both sit absolutely in this box's top corners, and the first row of circles
     reached right into them. */
  padding-top: 24px;
  /* stretch, not center: a centred column child takes its max-content width and
     overflows the card when that is wider than the card — which is exactly what
     three 96px circles in a row do at 360px. Stretched, each row is the card's
     width and wraps inside it. */
  align-items: stretch;
  gap: 14px;
  padding-bottom: 2px;
}
.kons-circles__row {
  display: flex;
  max-width: 100%;
  flex-wrap: wrap;
  justify-content: center;
  /* Bottom, not centre: the discs differ in size, and hanging them from a
     common baseline is what lets the reader compare them by area — centred,
     each caption sat at a different height and the row read as scattered. */
  align-items: flex-start;
  gap: 12px 14px;
}
/* A button, not a div: this is the press that filters the case table under it,
   and on a phone it is the only way to that table. Native button styling reset
   so it still reads as part of the drawing. */
.kons-circle-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  max-width: 96px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.kons-circle {
  border-radius: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 1.02;
  color: #fff;
  flex-shrink: 0;
  transition: box-shadow 0.15s;
}
.kons-circle__value { font-size: 16px; font-weight: 700; }
.kons-circle__unit  { font-size: 9px; opacity: 0.85; }
.kons-circle__cap {
  font-size: 10.5px;
  font-weight: 600;
  line-height: 1.25;
  text-align: center;
  color: var(--text);
}
.kons-circle__cap span {
  display: block;
  font-weight: 500;
  color: var(--text-muted);
}
/* The selected stage, matching what a pressed node wears on the desktop flow:
   the same dark ring, here as a halo the disc can carry at any size. */
.kons-circle-item--active .kons-circle { box-shadow: 0 0 0 3px #15263b; }
.kons-circle-item--active .kons-circle__cap { color: #15263b; }

/* Avslått, Venter svar and Godkjent are the three OUTCOMES of Til behandling —
   siblings under one parent in the flow tree. On the desktop a ribbon shows
   that; here the box does, with the parent node as its caption (and pressable,
   so the whole of Til behandling is still one tap away). Grey, because that is
   the colour KONS_STAGE_META gives tilBehandling. */
.kons-circles__group {
  align-self: stretch;
  border: 1.5px solid #8a8a8a;
  background: #f2f4f6;
  border-radius: 12px;
  padding: 8px 8px 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.kons-circles__group-cap {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: #4a5561;
  cursor: pointer;
}
.kons-circles__group-cap span {
  display: block;
  margin-top: 2px;
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--text-muted);
}
.kons-circles__group-cap--active { color: #15263b; text-decoration: underline; }

/* ── Energikostnader comparison — always visible below the 4 toggled views ── */
.kons-energi-section {
  margin-top: 32px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

.kons-energi-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

  /* Section titles run at --fs-kpi-meta, the size of the front page's
     "Nok til a dekke arsforbruket til ..." line. Set as the TOKEN, never its
     22px: it is redefined at the phone breakpoint and a literal would stop
     following. See the KPI type-scale note at the top of this file. */
.kons-energi-title {
  font-size: var(--fs-kpi-meta);
  font-weight: 700;
  color: var(--text);
}

/* Under the bars now rather than under the heading, so the margin flipped with
   it: it needs air above it, and nothing follows it in the section. */
.kons-energi-subtitle {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 10px;
}

.kons-energi-mode-toggle,
.kons-energi-metric-toggle {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.kons-energi-metric-toggle {
  margin-bottom: 16px;
}

.kons-energi-metric-toggle[hidden] {
  display: none;
}

.kons-energi-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  max-width: 1100px;
}

/* Width cap removed with the grid view: this is now the only energi view and it
   is the panel's main content, so it uses the panel — the same reason the flow
   and the Utbygging chart do. (display/flex come from the
   #kons-energi-charts.kons-energi-single rule further up.) */
.kons-energi-single {
  max-width: none;
}

.kons-energi-card {
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 16px;
}

.kons-energi-card--large {
  padding: 20px 24px;
}

.kons-energi-card--large .kons-energi-card__title {
  font-size: 15px;
  margin-bottom: 16px;
}

.kons-energi-card--large .kons-energi-row {
  margin-bottom: 10px;
}

.kons-energi-card--large .kons-energi-row__label {
  flex-basis: 170px;
  font-size: 12.5px;
}

.kons-energi-card--large .kons-energi-row__track {
  height: 16px;
}

.kons-energi-card--large .kons-energi-row__val {
  flex-basis: 50px;
  font-size: 12.5px;
}

.kons-energi-card__title {
  font-size: 12px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 10px;
}

.kons-energi-card__unit {
  font-weight: 500;
  color: var(--text-muted);
}

/* Every row is a target: picking one shades the map by that technology's figure
   (see renderEnergikostnaderCharts). */
.kons-energi-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  cursor: pointer;
  border-radius: 6px;
  padding: 2px 4px;
  margin-inline: -4px;
  transition: background 0.15s;
}
.kons-energi-row:hover {
  background: rgba(0, 0, 0, 0.04);
}

/* The row the reader has PRESSED — the technology driving the map. Marked with
   a tinted plate, NOT by recolouring the bar: the bar's colour is reserved for
   "this is småkraft", and the two must not compete. Any row can take it,
   Småkraft included, but nothing does before the first press — the map opens on
   Småkraft by default and a plate there on arrival only repeated what that
   row's colour, weight and height already said. See energiTechPressed. */
.kons-energi-row--selected {
  background: var(--tint-peach);
}
.kons-energi-row--selected:hover {
  background: var(--tint-peach-hover);
}
.kons-energi-row--selected .kons-energi-row__label,
.kons-energi-row--selected .kons-energi-row__val {
  color: var(--text);
  font-weight: 700;
}

/* Småkraft stays the biggest, bluest row whatever else is selected — the whole
   comparison exists to place it, so it must never recede into the list. That
   emphasis is independent of the --selected plate above: this says "this is the
   subject", the plate says "this is what the map is showing". */
.kons-energi-row--active {
  min-height: 30px;
}
.kons-energi-row--active .kons-energi-row__label {
  font-size: 13px;
}
.kons-energi-row--active .kons-energi-row__track {
  height: 22px;
}
.kons-energi-row--active .kons-energi-row__val {
  font-size: 15px;
}

.kons-energi-row__label {
  flex: 0 0 130px;
  font-size: 10.5px;
  color: var(--text-muted);
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.kons-energi-row--active .kons-energi-row__label {
  color: var(--text);
  font-weight: 700;
}

.kons-energi-row__track {
  flex: 1;
  height: 12px;
  background: var(--tint-grey);
  border-radius: 4px;
  overflow: hidden;
}

.kons-energi-row__fill {
  height: 100%;
  border-radius: 4px;
}

.kons-energi-row__val {
  flex: 0 0 40px;
  font-size: 10.5px;
  color: var(--text-muted);
}

.kons-energi-row--active .kons-energi-row__val {
  color: var(--text);
  font-weight: 700;
}

/* ─────────────────────────────────────────────────────────────
   Privat hjemfall section
   ───────────────────────────────────────────────────────────── */
/* Scroll hint button — floats at bottom-center of the verdier split view */
#slide-verdier .split { position: relative; }

.scroll-hint-btn {
  position: absolute;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 500;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 9px 18px;
  background: rgba(255, 255, 255, 0.95);
  border: 1px solid var(--border);
  border-radius: 24px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.14);
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
  transition: box-shadow 0.15s, background 0.15s;
}
.scroll-hint-btn:hover {
  background: #fff;
  box-shadow: 0 4px 16px rgba(0,0,0,0.18);
}
.scroll-hint-btn svg { color: var(--brand-green-deep); }

/* The panel is fixed to viewport height and clips overflow, so this section
   is a flex column: the top block takes its natural height and the chart
   section below absorbs whatever is left. Vertical padding scales with
   viewport height so the slide fits on shorter screens instead of clipping. */
.hjemfall-section {
  background: var(--tint-grey);
  border-top: 1px solid var(--border);
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.hjemfall-top {
  display: grid;
  grid-template-columns: 1fr 1.6fr;
  gap: 0;
  padding: clamp(16px, calc(2.6 * var(--sheet-vh)), 36px) 40px clamp(12px, calc(2 * var(--sheet-vh)), 28px);
  background: #fff;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.hjemfall-left {
  padding-right: 40px;
  border-right: 1px solid var(--border);
  display: flex;
  align-items: center;
}

.hjemfall-right {
  padding-left: 36px;
}

.hjemfall-body {
  font-size: 13px;
  line-height: 1.55;
  color: var(--text);
  max-width: 360px;
}

/* Steps diagram */
  /* Section titles run at --fs-kpi-meta, the size of the front page's
     "Nok til a dekke arsforbruket til ..." line. Set as the TOKEN, never its
     22px: it is redefined at the phone breakpoint and a literal would stop
     following. See the KPI type-scale note at the top of this file. */
.hjemfall-steps-title {
  font-size: var(--fs-kpi-meta);
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  margin-bottom: clamp(8px, calc(1.6 * var(--sheet-vh)), 18px);
}

.hjemfall-steps {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.hjemfall-step {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  padding-bottom: clamp(9px, calc(1.6 * var(--sheet-vh)), 18px);
  position: relative;
}

/* Vertical connector line between steps */
.hjemfall-step:not(:last-child)::after {
  content: '';
  position: absolute;
  left: 17px;
  top: 36px;
  bottom: 0;
  width: 1px;
  background: var(--border);
}

.hjemfall-step__icon {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text-muted);
  position: relative;
  z-index: 1;
}

.hjemfall-step__icon--active {
  background: var(--brand-green-deep);
  border-color: var(--brand-green-deep);
  color: #fff;
}

.hjemfall-step__body { flex: 1; padding-top: 4px; }

.hjemfall-step__num {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  margin-bottom: 2px;
}

.hjemfall-step--active .hjemfall-step__num { color: var(--brand-green-deep); }

.hjemfall-step__title {
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 3px;
}

.hjemfall-step__desc {
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.5;
}

.hjemfall-cycle {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--tint-green);
  border: 1px solid #b8d8a8;
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 12px;
  color: var(--brand-green-deep);
  font-weight: 500;
  margin-top: 4px;
}

.hjemfall-cycle svg { flex-shrink: 0; color: var(--brand-green-deep); }

/* Hjemfall chart */
/* Takes the remaining column height and lets the chart flex into it, rather
   than adding a fixed 220px block that pushes the slide past the fold. */
.hjemfall-chart-section {
  padding: clamp(12px, calc(2 * var(--sheet-vh)), 28px) 40px clamp(14px, calc(2.4 * var(--sheet-vh)), 36px);
  background: var(--tint-grey);
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* .hjemfall-chart-eyebrow and .hjemfall-chart-note are gone with the two
   paragraphs they styled — the title is drawn inside the plot now, at the same
   size and in the same place as every other chart's (drawTitleStrip). */

.hjemfall-chart-wrap {
  /* Plain white/bordered, same as Utbygging's .chart-wrap. The green tint that
     used to be painted into the canvas behind the plot is gone; the bars sit on
     this white directly. */
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px 16px 10px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
  /* A definite height, not flex:1. Its parent .verdi-graph is content-sized now
     that both of this slide's graphs are on screen at once, and inside a
     content-sized parent flex:1 resolves to the min-height floor — so this
     chart came out 120px, a third of every other chart in the app, while the
     column had room to spare. Sized like .chart-wrap's ceiling instead, with
     px bounds so it stays sane on a very short or very tall window: the column
     scrolls, and the scroll cue at its foot says so.
     Taller now by the ~34px the eyebrow and note paragraphs above it used to
     take, plus the strip's own headroom: the title moved inside the plot, so the
     card has to hold it, and without this the drawing area would have lost a
     whole title strip's height rather than trading one for the other. */
  height: clamp(300px, calc(48 * var(--sheet-vh)), 450px);
  flex: none;
  position: relative;
  /* Flex column, same as .chart-wrap: the canvas takes the free space and
     the standard .chart-legend markup now nested inside this box (see
     index.html) sits under it at its own natural height, inside the same
     card instead of as a second, differently-sized element below it. */
  display: flex;
  flex-direction: column;
}
.hjemfall-chart-wrap > canvas { flex: 1; min-height: 0; }
/* The legend swatch's colour has to match HJEMFALL_BAR in script.js - set
   inline in index.html rather than here, since .chart-legend-color has no
   chart-specific default colour of its own for any chart to override. */

/* ─────────────────────────────────────────────────────────────
   Kraftpris slide
   ───────────────────────────────────────────────────────────── */
/* Body content below the header — same role as .aaret-body/.bidrag-grid on
   the other slides, own padding now that .panel--dark carries none. */
/* A flex column so the chart section at the bottom can take the leftover height
   (see .kp-chart-section) instead of sitting at its content height and leaving
   grey below it. The prose above keeps its natural size — flex-shrink:0 — so the
   chart absorbs the slack rather than squeezing the text. */
.kp-body-wrap {
  /* Surface, inset and scrolling come from the shared slide-body rule near the
     top of this file. Two things are this slide's own: a flex column, and a
     bottom inset copied from .panel--map so the chart card's lower edge lands on
     the same line as the map card's. */
  padding-bottom: clamp(10px, calc(1.5 * var(--sheet-vh)), 20px);
  display: flex;
  flex-direction: column;
}

/* Everything above the chart holds its own height, so the chart takes what is
   left rather than squeezing the label and the cards (see .kp-chart-section's
   flex:1 below). */
.kp-body-wrap > .kpi-section-label,
.kp-body-wrap > .kpi-grid {
  flex-shrink: 0;
}

/* .kp-hero / .kp-hero__number / .kp-hero__unit / .kp-subtitle / .kp-divider /
   .kp-body are gone with the 88px hero they styled. The headline figure is a
   .kpi-card now, the same one every other slide leads with, and the two prices
   the old prose named are cards beside it. */

/* The KPI row needs the gap to the chart that a section label alone does not
   give it — same 14px .kpi-grid--select spends on the selector slides. */
.kp-body-wrap > .kpi-grid { margin-bottom: 14px; }

/* Per-zone bar chart at the bottom — the same .chart-wrap card Utbygging uses,
   with its title drawn inside the plot by konsBehBox like every other chart. */
.kp-chart-section {
  display: flex;
  flex-direction: column;
  /* Takes the height left over under the cards, so the chart's bottom edge
     meets the map's. min-height stays as the floor for short viewports. */
  flex: 1;
  min-height: 220px;
}

/* .kp-chart-eyebrow is gone with the paragraph it styled — Kraftpriseffekt's
   chart carries its title inside the plot now, like every other chart. */

/* ─────────────────────────────────────────────────────────────
   Daglig småkraftproduksjon timeseries (start slide)
   ───────────────────────────────────────────────────────────── */
/* ── Chart export menu (three dots) ──────────────────────────────────────────
   Sits in the chart card's top-right corner. Absolute so it does not take a row
   of its own above a chart that is already tight for height. */
.chart-menu {
  position: absolute;
  top: 4px;
  right: 6px;
  z-index: 30;
  gap: 6px;
  /* One hamburger per figure now — expand, save-image and download-data are all
     items in its list, so the separate expand button that used to sit beside it
     is gone. Kept as a flex row so the button centres predictably. */
  display: flex;
  align-items: center;
}

/* A COLUMN on the maps: hamburger, then the nedbør control, then kraftverk.
   The row it used to be ran out of width once there was more than one thing in
   it, and a corner stack is what every map control on the page already is.
   align-items:flex-end keeps them on the card's right edge whatever their
   widths. Above Leaflet's own panes (z-index 400+) or they would be under the
   map. */
.chart-menu--map {
  flex-direction: column;
  align-items: flex-end;
  top: 12px;
  right: 12px;
  z-index: 620;
}
/* Over a map it needs a stronger shadow to separate from the tiles; the white
   fill and border it already carries do the rest. */
/* ── Nedbør overlay ──────────────────────────────────────────────────────────
   The button is a .chart-menu__btn, so it inherits the hamburger's size, border
   and shadow outright and the two sit as one row. Only the state colours and
   the icon fill are its own — the drop is a filled path, not a stroked one like
   the hamburger, so it needs fill rather than stroke. */
/* Nested inside .chart-menu--map's flex row. Static, not the absolute the base
   .chart-menu is: it is a flex ITEM of that row, and its list anchors to it. */
.precip-menu {
  position: relative;
  top: auto;
  right: auto;
  z-index: auto;
}
/* The selected mode, in the open list. */
.chart-menu__item--on {
  color: #0050a1;
  font-weight: 700;
}
.precip-btn { position: relative; }
/* Kraftverk markers on/off. Active reads exactly like the nedbør button: white
   chrome, the turbine keeping its own colours, and a soft blurred blue disc
   behind it. Two switches on the same map strip, so the same "on" mark. */
.kv-btn { position: relative; }
.kv-btn__icon {
  width: 24px;
  height: 24px;
  object-fit: contain;
  display: block;
  /* Above the glow: the disc is drawn behind the symbol, never over it. */
  position: relative;
  z-index: 1;
}
.kv-btn--active { border-color: #2f7ec4; }
.kv-btn--active::before {
  content: '';
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(47,126,196,0.55), rgba(47,126,196,0.06) 72%);
  filter: blur(3px);
  z-index: 0;
}
.precip-btn__icon {
  width: 24px;
  height: 24px;
  display: block;
  /* Above the glow, and never filtered. The active state used to recolour this
     with brightness(0) invert(1), which made the cloud DISAPPEAR: precip_logo.svg
     paints two of its layers with mix-blend-mode multiply, and multiplying a
     forced-white shape against a white one leaves nothing at all. The selection
     is drawn behind the icon instead, so the icon is always just the icon. */
  position: relative;
  z-index: 1;
}
.precip-btn:hover { color: #0050a1; border-color: #9dc0e4; }
/* Selected: the button keeps its white chrome and the cloud keeps its colours.
   What changes is a soft blurred disc behind the icon plus a coloured rim —
   enough to read as "on" at 40px without painting over the symbol. */
.precip-btn--active { border-color: #2f7ec4; }
.precip-btn--active::before {
  content: '';
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(47,126,196,0.55), rgba(47,126,196,0.06) 72%);
  filter: blur(3px);
  z-index: 0;
}
/* Forecast is the other choice. Violet rather than blue, matching the top of
   the precipitation ramp, so which mode is on is legible from the button alone
   and not only from the legend's wording. */
.precip-btn--forecast { border-color: #7b2fc4; }
.precip-btn--forecast::before {
  background: radial-gradient(circle, rgba(123,47,196,0.55), rgba(123,47,196,0.06) 72%);
}

/* The colourbar while the overlay has taken it over. A press on .choro-legend
   toggles the CHOROPLETH, which is not what the bar is describing now and is
   hidden under the overlay anyway — so it stops advertising itself as pressable
   until the overlay is switched off again. */
/* Still pressable while precipitation owns it — the press cycles the overlay
   rather than the choropleth (see toggleChoro), so it keeps the pointer.
   It no longer needs the extra width the attribution line used to ask for:
   that line lives in the page footer now (see .site-footer__src), so this is
   a scale and nothing else, at the same width as the choropleth bar. */
.choro-legend--precip {
  opacity: 1;
}

.chart-menu--map .chart-menu__btn,
.chart-menu--map .chart-tool {
  box-shadow: 0 2px 7px rgba(0, 0, 0, 0.22);
}

/* Sized up by half again (26px → 40px, with the glyph doubled from 11px to
   22px) and given a bordered white tile: at the old size, over a busy chart or
   map, it read as a stray mark rather than a control, and people did not find
   it. The border and fill are what make it legible on any background. */
/* One tile, two jobs. .chart-menu__btn opens the figure's own menu;
   .chart-tool DOES something directly (zoom out, back to the whole year) and
   opens nothing. They must not share a class — .chart-menu__btn is what the
   page's single delegated menu handler listens for — but they should look like
   members of one cluster, so the look lives here once. */
.chart-menu__btn,
.chart-tool {
  border: 1.5px solid var(--border);
  background: #fff;
  color: var(--text-muted);
  line-height: 1;
  cursor: pointer;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.10);
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.chart-menu__btn {
  width: 40px;
  height: 40px;
  border-radius: 10px;
}
/* A size down from the hamburger beside them: that one is the figure's own menu
   and is always there, these are transient, and the three should not read as
   equal controls in a row. */
.chart-tool {
  width: 34px;
  height: 34px;
  border-radius: 9px;
}
.chart-tool[hidden] { display: none; }

/* Sized here rather than on the SVG's own width/height so the inline icon in
   index.html and the one skAttachMenu injects always match. */
.chart-menu__btn svg {
  width: 22px;
  height: 22px;
}
.chart-tool svg {
  width: 20px;
  height: 20px;
}

/* Hover: the pale fill alone. The blue border it used to gain as well drew a
   second outline around a tile that already has one, which read as the button
   having switched on rather than as the cursor being over it. Transparent
   rather than removed, so the box does not resize under the pointer.
   aria-expanded is the hamburger's own: its list being open IS a state, and
   the same treatment says so. */
.chart-menu__btn:hover,
.chart-tool:hover,
.chart-menu__btn[aria-expanded="true"] {
  background: var(--tint-blue);
  border-color: transparent;
  color: var(--brand-blue);
}

/* And straight back to white while the press is held. None of these is a
   switch: they do a thing and are done, and every one of them can be pressed
   again immediately — a tile that stays lit after the click reads as a toggle
   that is now on. Declared after :hover so it wins while both apply. */
.chart-menu__btn:active,
.chart-tool:active {
  background: #fff;
  border-color: var(--border);
  color: var(--text-muted);
}

.chart-menu__btn:focus-visible,
.chart-tool:focus-visible {
  outline: 2px solid var(--brand-blue);
  outline-offset: 2px;
}

/* ─────────────────────────────────────────────────────────────
   Expand-to-fill panel
   ───────────────────────────────────────────────────────────── */
/* A figure moved out of its card and given 80% of the window (see skZoomOpen).
   Above .modal's z-index so the hjemfall explainer cannot end up behind it, and
   above Leaflet's own control panes (.leaflet-top/.leaflet-bottom, z-index 1000
   in leaflet.css) — otherwise the "Søk kraftverk" box and "Vis kraftverk"
   toggle poke out above the blurred backdrop instead of being covered by it.
   .modal has since been raised over Leaflet for the same reason, so this moved
   up a step with it rather than tying. */
.sk-zoom {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: grid;
  place-items: center;
}
.sk-zoom[hidden] { display: none; }

/* The page is left visible but pushed out of focus, the same treatment the
   information boxes use — enough to read as "this is on top of the dashboard"
   without hiding where you were. */
.sk-zoom__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(21, 38, 59, 0.28);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
}

.sk-zoom__panel {
  position: relative;
  width: 80vw;
  height: 80vh;
  background: #fff;
  border-radius: 16px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.3);
  /* Top padding clears the close button; the figure gets the rest. */
  padding: 40px 22px 20px;
  display: flex;
  flex-direction: column;
}

.sk-zoom__close {
  position: absolute;
  top: 8px;
  right: 12px;
  width: 30px;
  height: 30px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text-muted);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}
.sk-zoom__close:hover { background: var(--tint-grey); color: var(--text); }

/* Save, on the panel's top bar beside the close. Only the openers that pass a
   save action get one (see skZoomOpen's opts.save), and it is hidden otherwise,
   so a figure with nothing to save shows no dead control.
   Right-offset past the close button rather than left of it in source order:
   both are absolutely positioned out of the panel's flex column so neither
   steals height from the figure, which is the whole reason the panel has a
   40px top padding instead of a header row. */
.sk-zoom__save {
  position: absolute;
  top: 8px;
  right: 50px;
  height: 30px;
  padding: 0 14px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  background: #fff;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.sk-zoom__save[hidden] { display: none; }
.sk-zoom__save:hover {
  background: var(--tint-blue);
  border-color: var(--brand-blue);
  color: var(--brand-blue);
}
/* Mid-capture: html2canvas is being fetched or is drawing, and a second click
   would start a second capture over the first. */
.sk-zoom__save:disabled {
  cursor: progress;
  opacity: 0.6;
}

/* The figure has to be told to fill the panel: in its card it was sized by a
   grid track that no longer applies here. min-height:0 so a flex child that
   draws to its container (every canvas on this page) can shrink as well as
   grow, instead of pinning the panel open at its content height. */
.sk-zoom__body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.sk-zoom__body > * {
  flex: 1;
  min-height: 0;
  width: 100%;
  max-width: none;
  max-height: none;
}

/* Already expanded — the panel's own close button is what you want now, and a
   "Stor skjerm" item that does nothing reads as broken. Save-image and
   download-data stay, since both still make sense from inside the panel. */
.sk-zoom__body .chart-menu__item[data-export="expand"] { display: none; }

/* The Småkraftåret KPI cards are a tall list, not a figure that fills the
   panel — scroll them instead of letting them push past the panel's edge. */
#aaret-kpi-body.is-zoomed {
  overflow-y: auto;
}

/* Holds the figure's place in the page while it is in the panel, so nothing
   behind reflows and it lands back where it started. */
.sk-zoom__placeholder { width: 100%; }

/* The page does not scroll behind an open panel. */
body.sk-zoom-open { overflow: hidden; }

.chart-menu__list {
  position: absolute;
  /* Below the button, whatever its height — it grew to 40px and this follows. */
  top: 100%;
  right: 0;
  margin-top: 4px;
  min-width: 152px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
  padding: 4px;
  display: flex;
  flex-direction: column;
}
.chart-menu__list[hidden] {
  display: none;
}

.chart-menu__item {
  border: none;
  background: none;
  font-family: inherit;
  font-size: 12.5px;
  color: var(--text);
  text-align: left;
  padding: 8px 10px;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
}
.chart-menu__item:hover {
  background: var(--tint-grey);
}

.sk-ts-section {
  /* Positioning context for .day-pop, which sits here as a sibling of the
     chart rather than inside it — the chart wrap has a fixed height on mobile
     and would squeeze the popup. Also the anchor for .chart-menu. */
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border-top: 1px solid rgba(0,0,0,0.07);
  margin-top: 16px;
  padding-top: 4px;
}

/* Phone-only stand-in for the in-canvas title skTsPeriodPlugin draws on
   desktop (see app/script.js) — that plugin skips drawing on phones since
   there's no headroom above the plot area there, so this DOM title carries
   the same text instead. Hidden by default; shown at the same breakpoint
   isPhone() uses in JS. */
/* Phone-only stand-in for the heading skTsPeriodPlugin draws into the canvas
   on wider screens (see skTsStrip in script.js). Same token as that one, so the
   chart's title is one size on this page whichever path renders it. */
.sk-ts-title-phone {
  display: none;
  font-size: var(--fs-kpi-meta);
  font-weight: 700;
  color: var(--text);
  margin: 0 0 4px;
}

.sk-ts-chart-wrap {
  position: relative;
  flex: 1;
  /* A floor and a ceiling. flex:1 alone resolves to nothing when the parent
     column is unbounded — which happens on a short landscape phone, where
     .split's min-height exceeds the viewport — and Chart.js then draws a
     canvas of unbounded height. */
  min-height: 160px;
  /* No ceiling any more: this chart's height is now decided one level up, by
     .bidrag-ts-row's own clamped height. The old sheet-fraction cap was there
     because the figure sat inside a window-tall panel with nothing else
     bounding it; kept, it just left a strip of grey below the card inside the
     band. The floor stays — see above. */
  max-height: none;
  width: 100%;
  background: #fff;
  border-radius: 10px;
  border: 1px solid var(--border);
  padding: 8px 8px 4px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.05);
}

/* .sk-zoom__body > * resets max-height on the moved figure itself, but the
   expand target here is .sk-ts-section, one level up (see skZoomOpen call in
   script.js) — its own children, incl. this one, keep their card ceiling
   unless told otherwise, which left the zoomed panel mostly blank below a
   card-sized chart. */
.sk-ts-section.is-zoomed .sk-ts-chart-wrap {
  max-height: none;
}

/* ─────────────────────────────────────────────────────────────
   Day-detail popup (intraday curve for a clicked day)
   Floats over the daily chart, anchored to the half opposite the
   clicked day so it never covers the point it describes.
   ───────────────────────────────────────────────────────────── */
.day-pop {
  position: absolute;
  /* Measured from .sk-ts-section, which includes the header row — hence the
     larger offset than when this was anchored inside the chart wrap. */
  top: 46px;
  z-index: 20;
  width: min(46%, 380px);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 6px 24px rgba(0,0,0,0.16);
  padding: 10px 12px 8px;
  animation: dayPopIn 0.14s ease-out;
  /* Click-through. This box floats over ~46% of the plot, and positionDayPopup
     anchors it RIGHT for any day in Jan–Jun — which at desktop widths puts its
     left edge in early August. Solid, it swallowed every click from there to
     December, and the only way to move it left was to click a Jul–Dec day, i.e.
     one of the days it was covering: open a day in spring and August became
     unreachable for the rest of the session. The interactive parts below take
     pointer events back. */
  pointer-events: none;
}
/* Only the real controls and the mini chart take pointer events back.
   .day-pop__head used to be in this list, and it is a full-width band across the
   top of the popup — which sits over the top of the main plot, exactly where the
   historical band peaks from September on (the yearly max falls in mid-December).
   So the most clickable-looking part of every autumn day was dead: the press
   landed on the header, matched no handler and did nothing, and only a second
   press aimed lower down opened the day. The header is just a container for the
   two arrows and the × — those keep their own pointer events below, and the date
   and total beside them are not interactive — so the band can pass presses
   straight through to the chart underneath.
   The mini chart still takes presses in its own footprint (it needs hover for
   its tooltip), but every press over the popup is forwarded to the day
   underneath — see the .day-pop click handler in script.js. */
.day-pop__step,
.day-pop__close,
.day-pop__chart-wrap { pointer-events: auto; }

.day-pop[hidden] { display: none; }

/* Anchor side — set by positionDayPopup() */
.day-pop--left  { left: 12px; }
.day-pop--right { right: 12px; }

@keyframes dayPopIn {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.day-pop__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.day-pop__title {
  font-family: 'Satoshi', sans-serif;
  font-weight: 700;
  font-size: 13px;
  color: var(--brand-blue-deep);
}

/* Prev/next day steppers, either side of the title. */
.day-pop__step {
  border: none;
  background: transparent;
  font-size: 20px;
  line-height: 1;
  padding: 0 4px;
  color: var(--brand-blue);
  cursor: pointer;
  flex-shrink: 0;
}
.day-pop__step:hover:not(:disabled) { color: var(--brand-blue-deep); }

/* At the edge of the span the chart is showing (the year, or a zoomed window —
   see dayStepBounds in script.js) there is nowhere to step to. Greyed rather
   than hidden: the arrow keeps its place in the header, so the title beside it
   does not shift as you reach the end. */
.day-pop__step:disabled {
  color: var(--border);
  cursor: default;
  /* Click-through while it is dead: a disabled button still takes the press and
     fires nothing, which is another silent hole in the popup over the plot. */
  pointer-events: none;
}

.day-pop__head { gap: 4px; }
.day-pop__title { flex: 0 0 auto; text-align: center; }

/* Aggregated production for the open day — sits right after the date, in the
   chart's blue rather than the title's ink, so it reads as the one dynamic
   number in an otherwise static header (same treatment as the main chart's
   in-graph total next to "Daglig produksjon"). */
.day-pop__total {
  flex: 1;
  text-align: center;
  font-family: 'Satoshi', sans-serif;
  font-weight: 700;
  font-size: 12px;
  color: var(--brand-blue, #0050a1);
}

.day-pop__close {
  border: none;
  background: transparent;
  font-size: 18px;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0 2px;
}

.day-pop__close:hover { color: var(--brand-blue); }

.day-pop__sub {
  font-size: 10.5px;
  color: var(--text-muted);
  margin-top: 1px;
  margin-bottom: 6px;
}

.day-pop__chart-wrap {
  position: relative;
  height: 132px;
  width: 100%;
}

.day-pop__legend {
  display: flex;
  gap: 14px;
  margin-top: 6px;
  font-size: 10px;
  color: var(--text-muted);
}

.day-pop__leg { display: flex; align-items: center; gap: 5px; }

/* Data source, pushed to the right of the legend row. Which feed a figure
   comes from matters here — the intraday curve is always ENTSOE, while the
   daily total it reconciles to may be Elhub. */
.day-pop__source {
  margin-left: auto;
  font-size: 9.5px;
  color: var(--text-muted);
  white-space: nowrap;
}

.day-pop__sq {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex-shrink: 0;
}

.day-pop__sq--band { background: rgba(0,80,161,0.18); }
.day-pop__sq--line { background: var(--brand-blue); height: 3px; border-radius: 1px; }

/* The daily chart accepts clicks — hint it */
#sk-ts-chart { cursor: pointer; }

/* ─────────────────────────────────────────────────────────────
   KPI cards that drive the chart below
   Clicking one switches the timeseries between production,
   skatteinntekter and grunneierinntekter. The active card is
   tinted a cooler, darker shade than the surrounding white so it
   reads as pressed.
   Effekt and YTD share data-chart="production" and highlight
   together — they are one control for the same view. The two
   income boxes do not: each plots its own stream.
   ───────────────────────────────────────────────────────────── */
.kpi-switch {
  cursor: pointer;
}

/* A switch whose figure cannot honestly be given for the current selection.
   Today that is only Grunneierinntekter with one fylke picked: falleie is a
   share of that fylke's production, and a fylke's production is itself
   modelled, so the kroner figure would be an estimate on an estimate (see
   grunneierAvailable in script.js, which also swaps the value for a dash).
   Same 0.4-and-not-allowed treatment .geo-btn:disabled and
   .toggle-btn:disabled carry, so "off" looks the same everywhere on the page.
   The hover and peer-hover rules below are all class-only, so these two
   (attribute plus class) outrank every one of them and the box stays inert
   under the pointer instead of lighting up at 40 % opacity. */
.kpi-switch[aria-disabled="true"] {
  opacity: 0.4;
  cursor: not-allowed;
}
.kpi-switch[aria-disabled="true"]:hover,
.kpi-switch[aria-disabled="true"].is-peer-hover {
  background: none;
  box-shadow: none;
}

/* Unselected cards are plain white; selected cards go cooler and darker. The
   hover shade sits only a hair off white so that "hovered but not selected"
   can never be mistaken for "selected" — the selected tint is the darker,
   clearly separated step. The income cell is a plain wrapper rather than a
   card, so the switch lives on each .income-box — the whole box goes pale
   blue, same as effekt/ytd. */
.bidrag-cell--effekt.kpi-switch:hover,
.bidrag-cell--ytd.kpi-switch:hover,
.bidrag-cell--effekt.is-peer-hover,
.bidrag-cell--ytd.is-peer-hover {
  background: #fbfcfd;
}
.income-box.kpi-switch:hover,
.income-box.kpi-switch.is-peer-hover {
  background: #fbfcfd;
}

/* Selected. This is the page's default state for the two production cards. */
.bidrag-cell--effekt.kpi-switch--active,
.bidrag-cell--ytd.kpi-switch--active {
  background: #e4ebf2;
  box-shadow: inset 0 0 0 1px rgba(0,80,161,0.14),
              0 1px 4px rgba(0,0,0,0.08);
}
.income-box.kpi-switch--active {
  background: #e4ebf2;
  box-shadow: inset 0 0 0 1px rgba(0,80,161,0.14),
              0 1px 4px rgba(0,0,0,0.08);
}

/* Active + hover: one step darker again so the card still responds. The inset
   ring is repeated here because the generic hover rule below sets box-shadow
   and would otherwise drop it. */
.bidrag-cell--effekt.kpi-switch--active:hover,
.bidrag-cell--ytd.kpi-switch--active:hover,
.bidrag-cell--effekt.kpi-switch--active.is-peer-hover,
.bidrag-cell--ytd.kpi-switch--active.is-peer-hover {
  background: #dae4ee;
  box-shadow: inset 0 0 0 1px rgba(0,80,161,0.14),
              0 4px 14px rgba(0,0,0,0.12);
}
.income-box.kpi-switch--active:hover,
.income-box.kpi-switch--active.is-peer-hover {
  background: #dae4ee;
  box-shadow: inset 0 0 0 1px rgba(0,80,161,0.14),
              0 4px 14px rgba(0,0,0,0.12);
}

.kpi-switch {
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}

.kpi-switch:focus-visible {
  outline: 2px solid var(--brand-blue);
  outline-offset: 2px;
}


/* ═════════════════════════════════════════════════════════════
   PHONE  (≤640px)
   The desktop layout is a fixed-height dashboard: .split is one
   viewport tall and clips overflow. That model does not work on
   a phone, so here everything stacks and the page scrolls
   normally. Maps collapse behind a button; the region picker is
   mirrored out of the map so it stays reachable.
   ═════════════════════════════════════════════════════════════ */

/* ── Kraftverk detail card (phone) ────────────────────────────
   A floating map label cannot fit beside a narrow map, so on phones —
   in both orientations — marker details render into this card under the
   map instead. Hidden on desktop, where the floating label is used. */
.kv-detail { display: none; }

/* (not (pointer: fine)) on both branches: see the note on the first stacked
   breakpoint, above .scroll-cue. */
@media (max-width: 640px) and (not (pointer: fine)),
       (max-height: 560px) and (orientation: landscape) and (not (pointer: fine)) {
  .kv-detail:not([hidden]) {
    display: block;
    margin-top: 10px;
  }

  /* The card is sized for a floating popup (fixed 240px); in flow it
     should use the full column width. */
  .kv-detail .kv-label__card {
    width: auto;
    max-width: none;
    box-shadow: 0 1px 4px rgba(0,0,0,0.10), inset 0 0 0 1px var(--border);
  }

  /* Same condition isPhone() checks in JS — this is exactly when
     skTsPeriodPlugin skips drawing its in-canvas title, so the DOM
     stand-in appears precisely when the canvas one disappears. */
  .sk-ts-title-phone { display: block; }
}

/* ── Choropleth legend ────────────────────────────────────────
   Names what the map's shading represents, and the range it spans.
   Sits bottom-right inside the map, above the zoom control. */
/* Pressable: the bar switches the map's shading on and off (see toggleChoro in
   script.js), so it must take pointer events — it was `none` back when this was
   a caption rather than a control. */
.choro-legend {
  /* Vertical layout, matching .map-legend height. zoom matches the scale applied. */
  zoom: 1.25;
  background: rgba(255, 255, 255, 0.94);
  border-radius: 6px;
  padding: 7px 9px 6px;
  box-shadow: 0 1px 5px rgba(0,0,0,0.15);
  font-family: 'Satoshi', sans-serif;
  min-width: auto;
  width: 50px;
  pointer-events: auto;
  cursor: pointer;
  user-select: none;
  transition: box-shadow 0.15s ease, background 0.15s ease;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.choro-legend:empty { display: none; }
.choro-legend:hover { background: #fff; box-shadow: 0 2px 9px rgba(0,0,0,0.22); }
.choro-legend:focus-visible {
  outline: 2px solid var(--brand-blue);
  outline-offset: 2px;
}

/* Switched off: the whole key sits back a step so the map reads as unshaded,
   but stays legible enough to invite the press. */
.choro-legend--off { background: rgba(255, 255, 255, 0.82); }
.choro-legend--off .choro-legend__label { color: var(--text-muted); }
.choro-legend--off:hover { background: rgba(255, 255, 255, 0.97); }

.choro-legend__hint {
  font-style: italic;
  white-space: nowrap;
}

/* Kraftpriseffekt's bar states a scale rather than switching one on, so it
   drops the affordances of the pressable one — see updateKraftprisLegend. */
.choro-legend--static { cursor: default; }

.choro-legend__label {
  font-size: 9px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 0;
  white-space: normal;
  text-align: center;
  max-width: 60px;
  line-height: 1.2;
}

/* Fixed-height line reserved above the bar, on or off — a switch between a
   number and nothing here must not change the bar's position. */
.choro-legend__max {
  font-size: 8px;
  font-weight: 700;
  color: var(--text);
  text-align: center;
  white-space: nowrap;
  height: 10px;
  line-height: 10px;
}

.choro-legend__bar {
  width: 14px;
  height: 55px;
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.10);
  flex-shrink: 0;
}

.choro-legend__min {
  font-size: 8px;
  color: var(--text-muted);
  text-align: center;
}

/* Hidden on phones — the map has no room to spare there, same reason the
   kraftverk legend is hidden. (not (pointer: fine)): see the note on the
   first stacked breakpoint, above .scroll-cue. */
@media (max-width: 640px) and (not (pointer: fine)),
       (max-height: 560px) and (orientation: landscape) and (not (pointer: fine)) {
  .choro-legend { display: none !important; }
}

/* Region picker — lives in each slide header, next to the geo toggle, at every
   screen size. It used to be a Leaflet control inside the map; a picker buried
   in a map at the bottom of the page is awkward to reach, and it belongs beside
   the KPIs it drives. */
.region-picker {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 8px;
  max-width: 320px;
}

.region-picker__select {
  flex: 1;
  min-width: 0;
  font-family: inherit;
  font-size: 13px;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fff;
  color: var(--text);
  cursor: pointer;
}

/* A phone on its side gets this whole block too, not a half-desktop variant of
   it. Landscape has more width to play with, and the layout used to spend it
   pairing cards up on one line and keeping the header in its side-by-side
   desktop arrangement — a second set of layout rules to reason about for a
   marginal saving, and one more place for something unforeseen to collide. One
   column either way; extra width just stays empty. The short-landscape block at
   the bottom of this file now only reins in HEIGHT, which is the one thing that
   genuinely differs.
   (not (pointer: fine)) on both branches: see the note on the first stacked
   breakpoint, above .scroll-cue near the top of this file — this is the main
   stacked-layout block, so it is the one this guard matters most for. */
@media (max-width: 640px) and (not (pointer: fine)),
       (max-height: 560px) and (orientation: landscape) and (not (pointer: fine)) {

  /* ── Page model: stack and scroll ───────────────────────── */
  .split {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto;
    height: auto;
    min-height: 0;
  }

  #slide-bidrag .panel--dark { overflow: visible; }
  .panel--dark,
  .panel--aaret {
    overflow: visible;
    height: auto;
    /* Without this, a grid item's min-width defaults to its content's
       intrinsic min-content width — a fixed-width child (e.g. Konsesjons'
       .kons-flow flowchart, 3 columns of 200px boxes) then forces this
       whole 1fr grid track wider than the viewport, dragging the map panel
       and the rest of the page along with it, even though .kons-flow itself
       already scrolls internally. */
    min-width: 0;
  }

  /* Desktop scales these with vh against a fixed-height panel. On phones the
     panel is auto-height and mobile 100vh shifts as the address bar hides, so
     pin them to their small end instead. */
  .hjemfall-section,
  .hjemfall-chart-section { display: block; flex: none; min-height: 0; }
  .hjemfall-top    { padding: 18px 16px 14px; }
  .hjemfall-number { font-size: 44px; }
  .hjemfall-chart-section { padding: 14px 16px 20px; }

  /* ── Nav: the mark and the three group circles share one row ──────────────
     They were stacked, which spent two bands of a short screen on navigation
     before any content. The mark is the only thing left of the lockup here
     (.nav-brand__text is already hidden below 1600px), so it costs ~30px and
     the circles still land within a few pixels of centre. */
  .nav {
    height: auto;
    flex-direction: row;
    align-items: center;
    gap: 10px;
    padding: 5px 12px;
    /* The one thing on a phone that does not scroll away. It is how you get
       from one page of the dashboard to another, and the three circles are
       useless at the top of a document several screens long. It costs about
       52px of permanent chrome, which is the whole reason it was static here
       before: back then the SLIDE header was the sticky one, and two stacked
       sticky bars would have eaten a third of a short screen. The slide header
       gives up its sticky below, so this is now the only bar that holds.
       It is also what .tab-bar.is-open measures top:100% against. Any
       positioned value does that job (static did not, which is why tapping a
       circle used to open the group card a full viewport height down, off the
       bottom of the screen), so sticky serves both purposes at once.
       z-index 1050, which is a different number from the 700 this carried while
       it scrolled away: a bar that HOLDS passes over the maps, and Leaflet puts
       its own zoom and layer controls at 1000 (.leaflet-top/.leaflet-bottom),
       its popups at 700. At 700 a map's + and - buttons would have slid over
       the nav as the reader scrolled past them. The nav is also a stacking
       context of its own, so the group card's 600 is resolved INSIDE this
       number rather than against the page. Still under .modal (1100) and
       .sk-zoom (1200), which belong on top of everything.
       Sticky works here despite the html/body overflow-x further down, because
       that rule is `clip` rather than `hidden` and so makes no scroll
       container. See its own note. */
    position: sticky;
    top: 0;
    z-index: 1050;
  }

  /* The brand and this empty tail share the leftover width equally, so the
     circles land on the page's centre line rather than the centre of whatever
     the mark leaves over. */
  .nav-brand { padding: 0; flex: 1 1 0; }
  .nav::after { content: ''; flex: 1 1 0; }

  /* ── Group circles ────────────────────────────────────────
     Replaces the scrolling strip: three taps' worth of navigation instead of
     eight chips fighting over one row. Each circle owns its group's brand
     colour through --c, so the filled (current) state, the ring and the icon
     all follow from one value. */
  .tab-groups {
    display: flex;
    flex: 0 0 auto;
    justify-content: center;
    gap: 20px;
    padding: 0;
  }

  .tab-circle {
    --c: var(--brand-blue-light);
    /* 42px, down from 46, the same step the desktop bar took. Still well
       past the 44px touch target once the 5px of nav padding either side is
       counted, since nothing sits above or below them in the bar. */
    width: 42px;
    height: 42px;
    flex-shrink: 0;
    /* A hairline in neutral grey, not 2px in the group's colour. The colour was
       carrying the group when the mark inside was a one-colour glyph; the mark
       is a picture with a coloured disc of its own now, and a second ring in a
       different hue beside it read as a mistake on the orange one and
       disappeared into the disc on the blue and green. The group's colour is
       still said, twice: by the ring on the current circle below, and by the
       tint the open card takes. This border's only job now is to hold the disc
       off the white bar. */
    border: 1px solid rgba(0, 0, 0, 0.12);
    border-radius: 50%;
    background: #fff;
    color: var(--c);
    display: grid;
    place-items: center;
    cursor: pointer;
    padding: 0;
    transition: background 0.15s, color 0.15s, box-shadow 0.15s;
  }
  .tab-circle--ring  { --c: var(--brand-orange); }
  .tab-circle--verdi { --c: var(--brand-green); }

  /* The mark is a picture now, not a glyph (see .tab-groups in index.html), so
     it fills the button instead of sitting inside it. The file is already a
     circle on transparency, so the disc meets the button's border with nothing
     between them. */
  .tab-circle__img {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
    /* Resting circles step back, which is the job the white-on-colour fill used
       to do: a picture cannot be inverted, so "not chosen" is carried by the
       mark being quieter than the chosen one rather than by a colour swap. */
    opacity: 0.72;
    filter: saturate(0.7);
    transition: opacity 0.15s, filter 0.15s;
  }
  .tab-circle.is-current .tab-circle__img,
  .tab-circle[aria-expanded="true"] .tab-circle__img {
    opacity: 1;
    filter: none;
  }

  /* Current = the group the visible panel belongs to. The fill that used to say
     this is under the picture and invisible, so it is said with a ring in the
     group's own colour instead. outline rather than box-shadow, because the
     open state below is a box-shadow and the two have to be able to show at
     once: a circle can be both the current group and the open one. */
  .tab-circle.is-current {
    background: var(--c);
    color: #fff;
    outline: 2px solid var(--c);
    outline-offset: 1px;
  }

  /* Open = the group whose list is showing, which is not necessarily the
     current one: opening a group does not switch panels. The grey ring is what
     says "open", and it stays on the filled current circle too — otherwise
     opening the group you are already in would give no feedback at all. */
  .tab-circle[aria-expanded="true"] {
    box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.10);
  }

  /* ...and the inside picks up a light wash of the group's own colour while its
     list is up, so a pressed circle reads as "you are choosing in here". Only
     for circles that are not already filled: the current one is solid in that
     same colour, and the ring above is its open state.
     Kept although the picture now covers it: it is what shows if an image ever
     fails to load, and it costs nothing while one does. */
  .tab-circle[aria-expanded="true"]:not(.is-current) {
    background: color-mix(in srgb, var(--c) 15%, #fff);
  }

  /* ── The group's panel list, as a floating card ───────────
     Closed by default; the circles open it. Absolute rather than in-flow so
     the dashboard never shifts under it, against .nav, which is positioned
     (relative here, sticky on desktop) precisely so that it is the containing
     block this card measures top:100% against. z-index above the sheet, same
     reasoning as .region-panel. */
  .tab-bar {
    display: none;
    position: static;
    transform: none;
    max-width: 100%;
    height: auto;
    justify-content: flex-start;
    padding: 0 10px 8px;
    /* Momentum scrolling, and the active chip stays snapped into view. */
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
  }

  .tab-bar.is-open {
    display: flex;
    flex-direction: column;
    gap: 2px;
    position: absolute;
    top: 100%;
    left: 10px;
    right: 10px;
    z-index: 600;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.18);
    padding: 6px;
    max-height: 70vh;
    overflow-y: auto;
  }

  /* The open card takes on its group's colour, so the link between the circle
     you pressed and the list that appeared is visible rather than remembered.
     A wash, not a fill — the chips inside still have to read as the content.
     The plain #fff first is not redundant: the card floats over the dashboard
     so its background must stay opaque, and that declaration is what a browser
     without color-mix() falls back to (same progressive-enhancement shape as
     the :has() rules this file already relies on). */
  .tab-bar.is-open[data-group="prod"] {
    background: #fff;
    background: color-mix(in srgb, var(--brand-blue) 10%, #fff);
    border-color: color-mix(in srgb, var(--brand-blue) 35%, var(--border));
  }
  .tab-bar.is-open[data-group="ring"] {
    background: #fff;
    background: color-mix(in srgb, var(--brand-orange) 10%, #fff);
    border-color: color-mix(in srgb, var(--brand-orange) 35%, var(--border));
  }
  .tab-bar.is-open[data-group="verdi"] {
    background: #fff;
    background: color-mix(in srgb, var(--brand-green) 10%, #fff);
    border-color: color-mix(in srgb, var(--brand-green) 35%, var(--border));
  }

  /* Show only the open group's chips. Membership comes from the group classes
     the strip already uses, so there is no second copy of the tab list. */
  .tab-bar.is-open[data-group="prod"]  .tab:not(.tab--prod),
  .tab-bar.is-open[data-group="ring"]  .tab:not(.tab--ring),
  .tab-bar.is-open[data-group="verdi"] .tab:not(.tab--verdi) {
    display: none;
  }

  /* Full-width rows in the card, not chips in a row. */
  .tab-bar.is-open .tab {
    width: 100%;
    text-align: left;
    border-radius: 8px;
    font-size: 14px;
    padding: 12px 14px;
    margin: 0;
  }

  .tab {
    font-size: 12px;
    padding: 8px 13px;
    scroll-snap-align: start;
  }

  /* Group tinting reads as noise at this size; keep only the active chip. */
  .tab--prod, .tab--ring, .tab--verdi { background: transparent; }
  :not(.tab--prod)  + .tab--prod,
  :not(.tab--ring)  + .tab--ring,
  :not(.tab--verdi) + .tab--verdi { margin-left: 4px; }

  .panel--map {
    border-left: none;
    border-top: 1px solid var(--border);
    padding: 12px 14px 16px;
  }

  /* ── Map ──────────────────────────────────────────────────
     The map is always shown on phones: it sits at the bottom of the
     slide, so hiding it behind a "Vis kart" button bought nothing. */
  .region-picker { max-width: none; }




  /* Controls that duplicate what the header already offers, or that
     cost more space than they are worth at this width. */
  .map-legend,                /* kraftverk legend — too tall for a phone */
  .leaflet-control-zoom {     /* pinch-zoom is the natural gesture here */
    display: none !important;
  }

  /* Slightly larger tap target than the base rule. */
  .region-picker__select { font-size: 14px; padding: 9px 10px; }

  /* Always visible: it sits at the bottom of the slide, so there is
     nothing to gain by hiding it behind a button. */
  .map-card {
    display: block;
    margin-top: 10px;
    height: 300px;
    flex: none;
  }

  /* ── Grids collapse to one column ───────────────────────── */
  .bidrag-grid,
  .kpi-grid,
  .aaret-kpi-grid { grid-template-columns: 1fr; }
  /* ...except the four Grunneiere metrics, which are a selector row: 2x2 keeps
     them one thumb-reach apart instead of four cards tall. */
  .verdi-cards--four,
  .verdi-cards--three { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Same reason as the 860px block: this query also fires on a short landscape
     phone that is wider than 860px, so the desktop's three equal 1fr rows have
     to be released here too or the stacked boxes get a third of the column
     each. */
  .bidrag-grid { grid-template-rows: none; grid-auto-rows: auto; }

  .bidrag-cell--ts { grid-column: 1; }

  /* Side-by-side blocks stack */
  .hjemfall-top,
  .effekt-kpi__body-row,
  .effekt-ytd-row { display: block; }

  /* ...but the figures inside must re-centre themselves. Stacking those rows
     drops the flex centering they relied on, and both of these are fixed-width
     BLOCK boxes (the donut canvas is sized in px by drawEffektDonut, the YTD bar
     is a 160px track), so the cards' text-align:center cannot pull them in —
     they sat 71px and 87px left of centre in a 334px card. */
  #effekt-donut,
  .ytd-hbar-outer { margin-inline: auto; }

  /* The bar is a fixed 320px, which works on desktop because it sits in a flex
     row (flex: 0 1 320px) that shrinks it when the card is narrow. The rule
     above turns that row into a block, so flex never applies and the 320px
     stood: on a 360px screen the card has about 250px of content width and the
     bar ran well past its own edge, which is the overflow on this card. Fluid
     now, capped at the width it was drawn for so a big phone still gets the
     full axis. margin-inline:auto above keeps it centred under the figure, the
     same place the donut sits on the card beside it. */
  .ytd-hbar-outer {
    width: 100%;
    max-width: 320px;
  }

  /* The 8px right inset this card carries on desktop was bought to give the
     320px bar room; with the bar fluid there is nothing to buy it for. Both
     cards get symmetric padding a few rules down, where the centring needs it
     anyway, so nothing is set here. */

  /* ...and so does the figure above them. Left-aligned it was the odd one out
     in a card whose heading, donut and bar are all centred, and it ran into the
     info dot in the card's top-left corner. Desktop keeps the figure on the
     heading's left axis (see .effekt-kpi__body-left) — there the donut sits
     out at the right and the pair reads as a row; stacked, there is no row for
     it to be the left half of. */
  .effekt-kpi__body-left { align-items: center; text-align: center; }
  .bidrag-cell .effekt-kpi__value-row { justify-content: center; }

  /* ── Everything in a box reads down one centre line ────────
     Desktop keeps these cards on a left axis: title, rule and figure share one
     edge, which is what makes four cards in a grid scan as a set. Stacked on a
     phone there is no set to scan across, each card is the full width of the
     screen, and the figures inside were already centred by the rules above —
     so the headings and the sentences under them were the only things still
     pulling left, against a centred number. The reference is the box shape
     Samfunnseffekter already uses (.ny-visning-box, centred on every screen).

     Symmetric padding is half the job: text-align centres inside the CONTENT
     box, so the cards' asymmetric insets (34px left to clear the corner (i),
     16px right) would have put the "centre" 9px off the card's real middle.
     34px both sides keeps the (i) clear and the middle true. */
  .bidrag-cell--effekt,
  .bidrag-cell--ytd {
    padding-left: 34px;
    padding-right: 34px;
    text-align: center;
  }

  /* The two income tickers, same treatment. Their rule is a 2.4em block that
     sat on the left edge under the heading; centred, it belongs under the
     middle of it. */
  .income-ticker {
    text-align: center;
    padding: 0 34px;
  }
  .income-ticker__rule { margin-inline: auto; }

  /* The "Tilsvarer …" lines beside the ticker on desktop, stacked under it
     here. They also pick up the horizontal inset the card itself does not
     carry (.income-box has no side padding, by design, so the ticker can span
     exactly one desktop grid column) — without it these sentences ran flush to
     the card's edge. Each line is an icon and a sentence in a flex row, so
     centring the text is not enough; the row has to centre too. */
  .income-equivalents {
    text-align: center;
    padding: 0 20px;
  }
  .income-equivalents__item { justify-content: center; }

  /* The closing line on the two production cards ("Nok til å dekke …", the
     timestamp) is a flex row as well. */
  .kpi-context {
    justify-content: center;
    text-align: center;
  }

  /* ── The small KPI cards ───────────────────────────────────
     126 of them across the slides, and the same change: centred text on a
     phone. The glyph is what made it a question rather than one line of CSS.
     It is absolutely positioned against the card's right edge with 80px of
     padding held open for it, so centring the text alone would have centred it
     inside that 80px-short box, visibly left of the card's middle, and the 105
     cards with no glyph at all would have been centred against a reserved
     space holding nothing. So the glyph comes into the flow and sits above the
     text, and the padding goes symmetric. The card grows by the height of the
     glyph on the 21 cards that have one; the rest get their full width back. */
  .kpi-card {
    padding: 14px 14px 16px;
    text-align: center;
  }
  /* Room held for the inline (i) that rides at the end of a label. Centred,
     the dot is inside the centred line and needs no room of its own. */
  .kpi-card__label { padding-right: 0; }
  .kpi-card__icon {
    position: static;
    transform: none;
    display: block;
    width: 44px;
    height: 44px;
    margin: 0 auto 8px;
  }

  /* ── Type scale ─────────────────────────────────────────── */
  .aaret-title   { font-size: 19px; }
  .slide-heading { font-size: 21px; }

  /* The day-detail popup overlays the chart on desktop, which would cover the
     whole plot at 360px. Here it drops into normal flow underneath instead —
     full width, no absolute positioning — so it reads as a detail panel rather
     than a tooltip. */
  /* Below the chart rather than over it — same treatment as the kraftverk
     detail card, so a narrow screen stacks instead of overlapping. */
  .day-pop {
    position: static;
    width: auto;
    margin-top: 10px;
    box-shadow: 0 1px 4px rgba(0,0,0,0.10);
    animation: none;
    flex: 0 0 auto;
  }
  .day-pop--left,
  .day-pop--right { left: auto; right: auto; }
  .day-pop__chart-wrap { height: 150px; }
  /* Finger-sized touch targets for the steppers and close button. */
  .day-pop__step,
  .day-pop__close {
    min-width: 40px;
    min-height: 40px;
    font-size: 24px;
  }
  .day-pop__title { font-size: 14px; }

  /* ── Overflow fixes ─────────────────────────────────────── */
  /* Nothing may push the page sideways; a single wide child would give the
     whole document a horizontal scrollbar.
     `clip`, not `hidden`: hidden on one axis makes the element a scroll
     container on BOTH (it computes to `hidden auto`), and a scroll container
     is what every position:sticky inside it measures against. With it on
     <body> nothing in this document could stick at all: the nav carries
     position:sticky and silently scrolled away here, which is how it went
     unnoticed. `clip` clips exactly the same overflow without creating a
     scroll container, which is what lets the nav hold its place at the top of
     a phone (see .nav in the phone block) while the page moves under it. */
  html, body { overflow-x: clip; }

  /* A ticker and its equivalent sit side by side with a gap up to 48px, which
     runs them off a 360px screen. Stack them instead — one rule covers both
     income boxes now that Grunneierinntekter uses the same .income-box__row. */
  .income-box__row {
    flex-direction: column;
    gap: 12px;
    /* The base rule's align-items is a VERTICAL alignment while this row is a
       row; stacked it becomes a horizontal one, so it has to be re-set here.
       stretch, so each child fills the column width and aligns its own text
       against the same left edge — flex-start would size each child to its
       content, which is the same edge but two boxes of different widths. */
    align-items: stretch;
  }
  /* Stacked, so there is no row remainder to claim: drop the flex: 1.
     align-items and justify-content are both HORIZONTAL here (the row has
     become a column, and each item is a row of its own), so both have to say
     centre for the sentence to sit under the middle of the figure it restates.
     They said flex-start while the ticker above ran down a left edge; the
     ticker is centred on a phone now, so the sentence follows it.
     align-self is horizontal once the row stacks too, so the desktop's
     flex-end would shunt the whole block to the card's right edge. */
  .income-equivalents { flex: none; align-items: center; align-self: stretch; }
  .income-equivalents__item { justify-content: center; }
  /* The three KPI sizes, restepped for a 360px column — one place, and every
     card on every slide follows. The title comes down because at 27px
     "Nåværende småkraftproduksjon" wraps to three lines here; the meta line
     tracks it down by the same ratio so the two never cross. */
  :root {
    --fs-kpi-title: 18px;
    --fs-kpi-meta: 15px;
  }
  /* Long values ("308 087 NOK") must wrap rather than extend the line box. */
  /* Nine digits do not fit a 360px column, so the unit is allowed to drop to
     a second line. Centred, with the rest of the stack: this rule is more
     specific than the .bidrag-cell one that centres every other value row, so
     leaving it at flex-start would have left these two figures the only
     left-aligned things in a centred card. */
  .income-ticker .effekt-kpi__value-row {
    flex-wrap: wrap;
    justify-content: center;
  }
  .effekt-kpi__value { overflow-wrap: anywhere; }

  /* Two- and three-column card grids collapse; --2 was not covered by the
     generic .kpi-grid override above. */
  .kpi-grid--2,
  .kons-energi-grid { grid-template-columns: 1fr; }

  /* ...but not the Utbygging/Konsesjon selector rows, which stay across at
     every width — see the 860px block for why and for the compact treatment
     that pays for it. Restated here because this query also fires on a short
     landscape phone WIDER than 860px, where that block never runs and the
     collapse above would be the last word. */
  .kpi-grid.kpi-grid--select {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
  }
  .kpi-grid--2.kpi-grid--select { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* Tighter card inset here: the slide body already spends its own inset a
     side, and renderKonsSankeyVertical draws a fixed 280px-wide flow sized
     against that remaining width. The desktop card padding would eat into it and push the
     flow into a sideways scroll on a 360px phone. */
  .kons-view { padding: 12px 8px 10px; }

  /* Samfunnsverdi's cards are selectors, not full-width readouts, so they get a
     compact phone treatment rather than the generic .kpi-grid collapse — left to
     stack full width they made this one slide ~1960px tall. Two to a row, which
     is what both of its rows now divide into: two recipients above, four
     Grunneiere metrics as 2x2 below. (It was three across when the rows held
     three and three.) Selector is .kpi-grid.verdi-cards so it beats the collapse
     rule regardless of which comes first in the file. */
  .kpi-grid.verdi-cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 6px;
  }
  .verdi-cards .kpi-card {
    padding: 8px 6px 10px;
    text-align: center;
  }
  /* These cards are centred chips at this width, a third the size they are on a
     desktop, and the 68px glyph the base rule gives every card landed straight
     on top of the figure (the padding that keeps them apart is gone here too).
     Small again, and in the flow above the label rather than absolute beside it,
     which is the only place a centred chip has room for it. */
  .verdi-cards .kpi-card__icon {
    position: static;
    transform: none;
    display: block;
    width: 24px;
    height: 24px;
    margin: 0 auto 3px;
  }
  .verdi-cards .kpi-card__label { font-size: 9px; margin-bottom: 4px; }
  .verdi-cards .kpi-card__value { font-size: 17px; }
  .verdi-cards .kpi-card__unit  { font-size: 9px; }
  .verdi-cards .kpi-card__sub   { font-size: 8.5px; margin-top: 4px; }
  /* Same job as the id-for-id repeats in the 860px block: the 18px rule on
     #verdi-hjemfall-cards outranks the two class rules above. */
  #verdi-hjemfall-cards .kpi-card__label { font-size: 9px; }
  #verdi-hjemfall-cards .kpi-card__sub   { font-size: 8.5px; }

  /* .verdi-graph is flex:none for every width now — see the base rule. */

  /* Case table on a phone: drop the columns that cannot earn their width at
     360px (kommune, status, innehaver — all still in the CSV export and on
     desktop) and let the rest use the full row. Project, MW, GWh and fylke are
     what identifies a case at a glance. */
  /* Height back to the content's: the fixed card exists to stop the flow above
     it resizing, and here the two are stacked, so there is nothing to protect
     and a part-empty card would just cost screen. */
  .kons-cases { padding: 10px 10px 4px; height: auto; display: block; }
  .kons-cases__scroll { flex: none; }
  .kons-cases__col--wide { display: none; }
  /* Status is kept (see KONS_CASE_COLS). It used to wrap here rather than widen
     the table; nothing wraps now — the fixed layout truncates instead, and the
     full value is in the cell's title. */
  .kons-cases__col--status { font-size: 11.5px; }
  .kons-cases__scroll { max-height: 300px; }
  /* Two columns are hidden here (.kons-cases__col--wide), so the eight that
     remain get their own floor rather than the desktop's 880px. The card
     scrolls sideways at this width — but it already did before any of this,
     at 628px in a 312px card, because "Konsesjonspliktvurdering" is one
     unbreakable word. Given that the scroll is there either way, the fixed
     layout is the better trade on a phone too: it buys every row a single line
     (111 of 111, against 69 when the cells wrapped) for the same sideways
     scroll. */
  .kons-cases__table { font-size: 12.5px; min-width: 620px; }
  .kons-cases__table th { padding: 6px 8px 7px 0; }
  .kons-cases__table td { padding: 7px 8px 7px 0; }
  /* Stacked, with the colour buttons on their own line and full sized — they are
     the one thing in this card that has to stay tappable. */
  .kons-cases__head { flex-direction: column; align-items: stretch; gap: 8px; }
  .kons-cases__toggles { gap: 6px; }
  /* Two to a row at most: with three destinations, stretching each to a third of
     the card wrapped "Venter på vedtak" onto a second line and left the row
     ragged. A 45% floor makes the third button take a new line instead, and each
     one stays a single line at a consistent height. */
  .kons-cases__toggle {
    flex: 1 1 45%;
    justify-content: center;
    padding: 7px 8px;
    white-space: nowrap;
  }

  /* Legends are below the plot everywhere now; just tighten them here. */
  .chart-legend { gap: 4px 12px; font-size: 10.5px; padding-top: 6px; }

  /* The eyebrow/region header wraps instead of forcing a wide line. */
  .eyebrow {
    flex-wrap: wrap;
    row-gap: 2px;
    word-break: break-word;
  }

  /* In-map search is sized for desktop and would overflow the card. */
  .kv-search { display: none; }

  /* ── Utbygging/Bidrag/Aaret header ──────────────────────────────
     Desktop puts the fylke pie beside the title column. At phone width
     that squeezes both: the pie shrinks to an unreadable disc and the
     title is left a narrow column. Stack instead, in this order:
     title+region, then the Fylker/Prisområder toggle, then the map+pie row
     (right under the selector it drives — the map needs the width freed up
     by dropping the side-by-side layout more than it needs to be near the
     title).
     .utbygging-header-left is display:contents so its own children
     (title, .geo-toggle) become direct flex items of the header alongside
     .utbygging-pie-wrap — that's what lets `order` place the pie row after
     the toggle without moving anything in the DOM.
     The metric cards were the 4th item in this stack until they moved out of
     the header and down onto the chart card (see .kpi-grid--select), so there
     is no order for them here any more.
     Three header classes share this shape: .utbygging-header (Utbygging
     only), .bidrag-header (Oppsummering only), .aaret-header (Småkraftåret,
     Leverandørindustrien, Konsesjon, Samfunnsverdi, Grunneiere, Kraftpris). */
  .utbygging-header,
  .bidrag-header,
  .aaret-header {
    display: flex;
    flex-direction: column;
    padding: 10px 14px 8px;

    /* In the flow, and it scrolls away like everything else. It used to be
       sticky here, on the argument that the region chip and the Fylker/
       Prisområder toggle are what every figure below is cut by and should stay
       reachable. In practice it meant the title, the pie and the toggle sat on
       top of the page the whole way down, and a phone has no room to spend on
       that: the only thing worth holding is the nav above, which is how you
       leave the page at all. The controls in this band are a scroll to the top
       away, the same as they are on any other page of any other site.
       It used to shrink as it went, too (.is-head-condensed), which is off now
       for a phone — see _noCondenseMQ in script.js. Both things are gone for
       the same reason: they were built for a desktop layout where the height
       this band gives up goes to the map beside it, and stacked there is
       nothing to give it to. */
  }

  .utbygging-header-left { display: contents; }
  /* Same order as the title, so DOM order puts it just under it rather than
     above everything (order defaults to 0, and the title's is 1).
     The reserved two lines are dropped here: this column is 360px wide, every
     description wraps to four or five lines at that measure, and there is no
     row of titles left to keep aligned once the header is a single stack. */
  .slide-intro {
    order: 1;
    margin: 6px 0 0;
    min-height: 0;
    /* auto, not the base block's fixed height (sized for its one line): every
       description here runs to four or five lines, and a height held to one
       line's worth would clip the rest once --head-condense started eating
       it. Opacity still fades with the scroll; the line just no longer
       has to close its own height to do it. */
    height: auto;
    max-width: none;
    font-size: var(--fs-kpi-meta);
    /* Undoes the one-line rule from the base block: every description runs to
       four or five lines at this measure, so holding them to one would leave
       an ellipsis after the first handful of words. Nothing is lost by
       wrapping here: there is no row of titles left to keep aligned once the
       header is a single stack. */
    white-space: normal;
    overflow: visible;
  }

  .utbygging-header .aaret-title,
  .bidrag-header .aaret-title,
  .aaret-header .aaret-title  { order: 1; }
  /* Same order as the title, so DOM order decides and it lands just under it.
     Without an order of its own it defaulted to 0 and sorted ABOVE everything —
     Samfunnsverdi's subtitle appeared before its own heading. */
  .utbygging-header .geo-toggle,
  .bidrag-header .geo-toggle,
  .aaret-header .geo-toggle   { order: 2; }
  /* The toggle stacks beside the pie on desktop; stacked here it would be two
     full-width rows in an already-tall column, so it goes back to a row. */
  .bidrag-header > .geo-toggle,
  .aaret-header > .geo-toggle,
  .utbygging-header > .geo-toggle {
    flex-direction: row;
    align-items: center;
    align-self: stretch;
    margin: 10px 0 6px;
  }
  .utbygging-pie-wrap         { order: 3; }

  /* Oppsummering's pie needs nothing of its own here any more: the desktop
     bar layout it used to undo (98px pie, caption beside it) is gone, so this
     header already carries the same stacked, full-size pie as the others. */

  /* The metric row's own phone treatment lives with the rest of it in the
     860px block above (.kpi-grid--select) — it is the same cards as every
     other slide's now, not a pill variant of its own. It only needs its
     bottom margin trimmed here. */
  .kpi-grid--select { margin-bottom: 10px; }

  /* Full-width row right under the geo toggle: mini map on the left, pie
     on the right, both given the room the side-by-side desktop layout
     couldn't spare. */
  .utbygging-pie-wrap {
    padding-top: 12px;
    margin-top: 10px;
    border-top: 1px solid var(--border);
    flex-direction: row;
    justify-content: center;
    gap: 16px;
    width: 100%;
  }

  /* ── Mini fylke map ─────────────────────────────────────────
     A tiny, click-only region picker next to the header pie — the full map
     is at the bottom of the slide, too far to reach for a quick region
     swap. Now that this row sits full-width under the geo toggle instead
     of squeezed beside the title column, it can afford to be sized well
     past the original beside-the-title version — unlike the pie, it carries
     no center label/percentage text that a bigger circle would just leave
     empty, so it can use the row's spare width directly. Still a fixed
     pixel size deliberately: it must never widen or grow the card, so it
     does not scale with viewport width like everything else in this block.
     The JS-side viewBox (MINI_MAP_W×MINI_MAP_H in script.js) stays fixed —
     the SVG just scales up to fill this box. */
  .mini-fylke-map {
    display: block;
    flex-shrink: 0;
    width: 120px;
    height: 166px;
    align-self: center;
  }
  /* The row is the mini map's height here, not the pie's — see the note on
     .card-loading--inflow for why this placeholder is sized at all. */
  .card-loading--inflow { height: 166px; }
  .mini-fylke-map svg { display: block; width: 100%; height: 100%; }

  /* ── Toggle rows ────────────────────────────────────────────
     Both groups on one line. They are short labels and wrapping them
     onto separate rows wastes vertical space on a slide that is
     already tall. */
  .toggle-row {
    flex-wrap: nowrap;
    gap: 8px;
    overflow-x: auto;
    scrollbar-width: none;
    /* Scaled down with everything else in this block: the desktop 10px on top
       of .kpi-grid--select's 10px here would be 20px of air on a slide this
       rule set exists to keep short. 6px keeps the gap above larger than the
       8px below, which is the point of it. */
    margin-top: 6px;
  }
  .toggle-row::-webkit-scrollbar { display: none; }
  .toggle-group { margin-bottom: 8px; flex-shrink: 0; }
  .toggle-btn { padding: 6px 11px; font-size: 11px; white-space: nowrap; }

  /* ── Chart heights ──────────────────────────────────────── */
  /* Every chart canvas runs with maintainAspectRatio:false, which means it
     takes its height entirely from its container. On desktop these wrappers
     are flex:1 inside a fixed-height panel, so that resolves. Here the panels
     are height:auto, so flex:1 resolves against nothing and the canvas grows
     without bound — the "infinitely long" plot. Pin an explicit height and
     cancel the flex so there is always something concrete to measure. */
  .chart-wrap,
  .sk-ts-chart-wrap,
  .hjemfall-chart-wrap {
    flex: none;
    min-height: 0;
  }
  /* Slightly taller than before: the legend now sits inside this box, below
     the canvas, so the plot needs the extra room to keep its old height. */
  .chart-wrap           { height: 300px; padding: 8px 6px 6px; }
  .sk-ts-chart-wrap     { height: 275px; padding: 8px 6px 4px; }

  /* ── The daily-production figure's two controls ─────────────
     On wider screens the (i) and the hamburger sit over the chart card's top
     corners, which is where the canvas draws its own title and where there is
     headroom to put them. Here the title is a DOM line ABOVE that card
     instead (skTsPeriodPlugin skips drawing it, see .sk-ts-title-phone), so
     the same two offsets left the dot printed over the title's first letters
     and the 40px hamburger straddling the gap between the title and the card,
     inside neither. Both belong to the title line: it becomes a proper header
     band, tall enough to hold the larger of the two controls, with the dot
     opening it and the menu closing it. The title's own padding is what keeps
     a long one from running under either, since all three overlap in the same
     40px of height. */
  .sk-ts-title-phone {
    display: flex;
    align-items: center;
    min-height: 40px;
    padding-left: 32px;
    padding-right: 48px;
    margin-bottom: 2px;
  }
  .sk-ts-section > .info-dot--chart { top: 12px; left: 2px; }
  .sk-ts-section > .chart-menu      { top: 4px;  right: 4px; }
  /* Shorter than the desktop clamp: on a phone this chart is one of several
     stacked cards, and 380px of it would push the Grunneiere cards a screen
     and a half up the page. */
  .hjemfall-chart-wrap  { height: 220px; }

  /* The canvas must not exceed the box either — Chart.js sets inline
     width/height attributes that can otherwise overshoot after a resize. */
  .chart-wrap canvas,
  .sk-ts-chart-wrap canvas,
  .hjemfall-chart-wrap canvas {
    max-height: 100%;
  }

  /* ── Konsesjons flowchart view ──────────────────────────────
     Fixed-width box-and-arrow layout (200px boxes × 3 columns + arrows)
     that doesn't reflow — same "let it scroll sideways" treatment the
     Sankey view already uses at every width, rather than clipping
     (html/body overflow-x:hidden above would otherwise just cut the
     right-hand boxes off entirely). The funnel/pictogram views are
     already fluid-width and need no override. */
  .kons-flow {
    max-width: 100%;
    overflow-x: auto;
  }
  .kons-flow__row { flex-shrink: 0; }
}

/* ═════════════════════════════════════════════════════════════
   SHORT LANDSCAPE  (wide but < 560px tall — a phone on its side)
   These viewports get the full phone layout above — one column, stacked,
   scrolling — because a second layout model earned less than it cost. All
   that is left here is HEIGHT: anything sized against a tall viewport, or
   flexed against a fixed-height panel, has to be reined in for a viewport
   under 560px tall. Nothing in here should reintroduce side-by-side.
   (not (pointer: fine)): see the note on the first stacked breakpoint, above
   .scroll-cue near the top of this file.
   ═════════════════════════════════════════════════════════════ */
@media (max-height: 560px) and (orientation: landscape) and (not (pointer: fine)) {
  .split { height: auto; min-height: 0; }
  /* Same release as the 860px block — needed again because a short landscape
     window can be wider than 860px and so miss it. */
  .slide { height: auto; }
  .slide > .split { flex: none; }
  .bidrag-ts-row { height: auto; }

  /* min-width:0: a CSS grid item (this panel, in the .split 3fr/2fr grid)
     defaults to min-width:auto — it won't shrink below its content's
     intrinsic width no matter how narrow its fr track is asked to be. That
     silently let the header (title + toggle + pills + mini map + pie) grow
     past the viewport with overflow:visible just letting it spill off the
     edge instead of wrapping. Same fix the portrait block already applies
     for the same reason. */
  .panel--dark,
  .panel--aaret,
  .panel--utbygging { overflow: visible; height: auto; min-width: 0; }

  /* Fixed heights rather than viewport fractions: 46vh of 393px is 181px,
     which is too short to read. */
  .sk-ts-chart-wrap { height: 220px; max-height: none; flex: none; }
  .chart-wrap       { height: 240px; max-height: none; flex: none; }
  .map-card         { height: 260px; }

  /* Mini region map: same stacked position as portrait (the pie wrap is a
     column by default and stays one here), just scaled down — 166px of mini
     map is a lot of a 393px-tall viewport. Size is the only thing this
     viewport needs to change about it. */
  .mini-fylke-map {
    width: 58px;
    height: 80px;
  }
}
