/* ============================================================
   Det här är prototypens stilmall (prototype/style.css), kopierad
   till produktionskoden av M1-4 · del skal. Fyra avsiktliga avsteg
   från prototypen: sökvägarna i @font-face nedan — fonterna ligger
   i ../fonts/ här, inte i fonts/ som i prototypen — de två
   extra deklarationerna i .view-head h1 (min-width, overflow-wrap),
   se kommentaren vid regeln — det statiska mappulse-läget i
   @media (prefers-reduced-motion: reduce) nedan (M5-6c), se
   kommentaren vid den regeln — och pointer-events: none på
   .pmap-mini svg (M5-8b), se kommentaren vid den regeln. Listan
   gäller AVSTEG från en prototypregel som redan fanns — den räknar
   inte ytor som tillkommit efter M1-4 och som prototypen aldrig
   ritade (konkurrentkortet, M9-4; utskriftssidan, M9-8a) - de har
   ingen motsvarande prototypregel att avvika från, se respektive
   yttas egen kommentar nedan. Ändras inte fristående; se
   prototype/style.css för originalet och FrontendAssetTests för
   kopians tokenlikhetskontroll.
   ============================================================ */

/* ============================================================
   SeoAnalys — prototyp
   Implementerar prototype/DESIGNSPEC.md. Två teman, mörkt som grundton.
   Tokenuppsättningen i :root är spec §4 rakt av.
   ============================================================ */

@font-face {
  font-family: "Inter";
  src: url("../fonts/inter-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("../fonts/inter-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("../fonts/inter-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Tema. Mörkt är grundtonen och det portalen byggdes i; ljust är ett likvärdigt val
     (beställarbeslut 2026-08-31, som upphäver Designgrund beslut 2). Varje färgtoken bär
     BÅDA värdena i light-dark() och är därmed deklarerad på exakt ett ställe - vilket av
     dem som gäller avgörs helt av color-scheme, som sätts av de tre reglerna under det
     här blocket. Ett andra, parallellt tokenblock hade kunnat glida isär från det här
     utan att något test märkte det. */
  color-scheme: dark;

  /* Ytor. Mörkt: --bg är mörkast och korten lyfter. Ljust: rollerna vänder - papperet är
     tonat och kortet är vitast, eftersom "vitare än vitt" inte finns. Spannet är smalare
     i ljust läge och kanterna bär mer av djupet i gengäld. */
  --bg:            light-dark(#eef1f5, #0e1116);   /* ljust: svalt papper, kylig neutral som klär indigon */
  --bg-elev:       light-dark(#ffffff, #161b22);   /* kortet - det upplysta lagret i båda temana */
  --bg-elev-2:     light-dark(#e7ebf1, #1c232c);   /* hovrad rad, fält, chip - går UNDER kortet i ljust */
  --border:        light-dark(#d7dde6, #262d38);
  --border-strong: light-dark(#b3bdcc, #333c49);

  /* Text — faint bär informationsbärande 11–13 px och måste klara AA även mot
     --bg-elev-2, som är den svåraste ytan (hovrad rad, fält) i båda temana.
     Ljust: 5,49:1 mot --bg-elev · 4,59:1 mot --bg-elev-2.
     Mörkt: 5,28:1 / 4,84:1 (höjd från #6b7885, som bara gav 3,8:1). */
  --text:       light-dark(#1b2330, #e6edf3);   /* ljusets bläck bär mörkrets eget blåstick */
  --text-muted: light-dark(#4a5568, #9aa7b5);
  --text-faint: light-dark(#5d6a7d, #8090a0);

  /* Accent — interaktion, aldrig rörelse.
     --accent bär fokusringar och pulser; --accent-btn är knappytan, avstämd så att vit
     text klarar AA även i hovertillstånd; --accent-ink är den ton graf- och kartbläck
     behöver för 3:1 som grafik. Hela familjen går MÖRKARE vid hover i ljust läge och
     ljusare i mörkt - riktningen är "mer kontrast", inte "ljusare". */
  --accent:           light-dark(#4f52e8, #6366f1);
  --accent-hover:     light-dark(#4144d6, #7c7ff5);
  --accent-btn:       light-dark(#585ce8, #5457e0);   /* vit text 5,11:1 (ljust) · 5,50:1 (mörkt) */
  --accent-btn-hover: light-dark(#494dd9, #4a4dd4);   /* vit text 6,27:1 (ljust) · 6,35:1 (mörkt) */
  --accent-ink:       light-dark(#4c50dd, #7c7ff5);   /* 6,00:1 (ljust) · 5,12:1 (mörkt) mot --bg-elev */
  --accent-dim:       light-dark(rgba(79, 82, 232, 0.12), rgba(99, 102, 241, 0.15));

  /* Signal — rörelse, aldrig interaktion. Mörkrets gröna och röda lyser mot svart men ger
     ~1,9:1 mot vitt: i ljust läge mörkas båda kraftigt, och dim-plattornas alfa sänks
     eftersom en pastellplatta behöver mindre pigment på vit botten.
     --up 6,31:1 och --down 5,78:1 på sina respektive plattor över kortet. */
  --up:        light-dark(#116329, #3fb950);
  --up-dim:    light-dark(rgba(17, 99, 41, 0.13), rgba(63, 185, 80, 0.14));
  --down:      light-dark(#b02a20, #ff7b73);
  --down-dim:  light-dark(rgba(176, 42, 32, 0.11), rgba(255, 123, 115, 0.13));
  --flat:      light-dark(#4a5568, #9aa7b5);   /* "oförändrad" visas som 0 i muted, inte som streck */

  /* Svävande lager: menyn, grafens värdeetikett och dialogens scrim. --bg-elev-2 duger inte
     som menyyta: den ligger ÖVER kortet i mörkt läge men UNDER det i ljust, så en meny som
     lånar den lyfter i mörkret och blir en grå urgröpning i vitt papper. Lagret får därför
     en egen token med samma vändning som --bg-tip. "Mörkare än mörkast" finns
     inte på vitt papper, så etiketten blir kortets egen vita yta med skugga i ljust läge,
     och scrimen tunnas ut och läggs i bläckets ton i stället för i ren svärta. */
  --bg-float:       light-dark(#ffffff, #1c232c);
  --bg-float-hover: light-dark(#e7ebf1, #161b22);
  --bg-tip:         light-dark(#ffffff, #0b0e13);

  /* Topplistens yta. I mörkt läge är den flush med sidan och skiljs bara av sin kant.
     Ljust papper går inte att göra så: --bg-elev-2 (hoverytan för växlare, chip och
     flikar) ger bara 1,065:1 mot papperet, och hela topplistens hoveraffordans hade
     försvunnit för musanvändaren. Vit topplist ger samma 1,20:1-steg som i mörkt. */
  --bg-topbar: light-dark(#ffffff, #0e1116);
  --backdrop: light-dark(rgba(27, 35, 48, 0.60), rgba(5, 7, 10, 0.65));

  /* Typsnitt */
  --font: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "Consolas", monospace;

  --fs-xs:   11px;
  --fs-sm:   13px;
  --fs-base: 14px;
  --fs-md:   16px;
  --fs-lg:   20px;
  --fs-pos:  24px;   /* höjd från 22 — spec §3.2 */
  --fs-xl:   36px;   /* höjd från 28 — spec §3.1, endast domännamnet */

  --fw-normal: 400;
  --fw-medium: 500;
  --fw-bold:   600;

  /* Mellanrum (4 px-bas), form, rörelse */
  --s1: 4px;  --s2: 8px;   --s3: 12px;  --s4: 16px;
  --s5: 24px; --s6: 32px;  --s7: 48px;  --s8: 64px;

  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;

  --row-h: 48px;

  --dur-fast: 120ms;
  --dur:      200ms;
  --dur-slow: 300ms;
  --ease:     cubic-bezier(0.2, 0, 0, 1);

  /* Topplistens totalhöjd — tabellernas rubrikrader fäster här under */
  --topbar-h: 57px;

  /* Kartans och grafens dämpade bläck. Opacitet är inte en färg och kan därför inte bära
     light-dark(): värdena sätts i temareglerna nedan. Grunden är mörkrets.

     --op-ink-faint: axlar, avgränsare, minikartans staplar och grafens hjälplinjer. 0,7 mot
     mörkt ger 3,27:1 mot kortet och 3,09:1 mot --bg-elev-2, men bara 2,95:1 mot vitt papper.
     0,80 i ljust ger 3,60:1 / 3,18:1 - samma marginal över 3:1, inte mer: 0,85 hade gett
     3,98:1 och låtit hjälplinjerna konkurrera med datat.

     --op-ink-off: bortfiltrerade streck i positionskartan, som fortfarande är klickbara
     kontroller och därför omfattas av 3:1 (§8.7). Måttet sätts av det SVÅRASTE underlaget,
     >50-zonens platta, inte av kortet: där ger 0,45 mörkt 3,52:1 och 0,55 ljust 3,19:1.
     De gamla 0,38/0,50 gav 1,72:1 respektive 1,77:1 - strecken i zonen var i praktiken
     borta i båda temana. Se även .pmap .hit.off .tick, som byter fyllnad till --text. */
  --op-ink-faint: 0.7;
  --op-ink-off:   0.45;

  /* Selectens pildekor. En data-URI är ingen färg och kan inte bära light-dark(): ikonen
     sätts därför i sin helhet, och ljusets version i temareglerna nedan. Strecket är
     --text-muted i respektive tema - mörkrets ton mot ett ljust fält gav bara 2,05:1, under
     3:1 för grafik, och pilen är selectens enda affordans (appearance: none). */
  --ic-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.5 1.75 6 6.25l4.5-4.5' stroke='%239aa7b5' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  /* Dialogens och värdeetikettens skugga. Mot mörkrets scrim bär kanten och ytnivån redan
     hela separationen (§8.2: skuggor används inte), men mot vitt papper gör de det inte -
     där behövs skuggan för att lagret ska läsa som svävande. Ingen skugga är ingen färg
     och kan därför inte bära light-dark(); ljusets värde sätts i temareglerna nedan. */
  --shadow-panel: none;

  /* Skugga används bara för svävande lager (meny, dialog, värdeetikett) — se spec §8.2.
     En skugga är ingen färg och kan därför inte bära light-dark(): ljusets värde sätts i
     temareglerna nedan. Mörkrets täta svärta blir en smutsfläck mot vitt papper, så ljust
     läge får i stället två lager - en tät kontaktskugga och en gles sval spridning i
     bläckets ton. */
  --shadow-float: 0 8px 24px rgba(0, 0, 0, 0.45);
}

/* De tre reglerna som avgör vilket värde varje light-dark() ovan landar på. "auto" är
   serverns värde när besökaren inte valt något (ThemePreference i produktionskoden,
   localStorage i prototypen) och lämnar avgörandet till operativsystemet; de två
   uttryckliga valen står sist och vinner därför över det.

   --ic-sun/--ic-moon styr vilken ikon temaväxlaren visar. Knappen erbjuder det tema den
   BYTER TILL, så mörkt läge visar en sol. Fallvärdena står vid .themetoggle-reglerna, inte
   här, så att :root fortsätter vara enbart palett. */
@media (prefers-color-scheme: light) {
  :root[data-theme="auto"] {
    color-scheme: light;
    --op-ink-faint: 0.80;
    --op-ink-off:   0.55;
    --shadow-float: 0 2px 6px rgba(27, 35, 48, 0.08), 0 12px 32px rgba(27, 35, 48, 0.16);
    --shadow-panel: 0 2px 6px rgba(27, 35, 48, 0.08), 0 12px 32px rgba(27, 35, 48, 0.16);
    --ic-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.5 1.75 6 6.25l4.5-4.5' stroke='%234a5568' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    --ic-sun: none;  --ic-moon: block;
  }
}
:root[data-theme="light"] {
  color-scheme: light;
  --op-ink-faint: 0.80;
  --op-ink-off:   0.55;
  --shadow-float: 0 2px 6px rgba(27, 35, 48, 0.08), 0 12px 32px rgba(27, 35, 48, 0.16);
  --shadow-panel: 0 2px 6px rgba(27, 35, 48, 0.08), 0 12px 32px rgba(27, 35, 48, 0.16);
  --ic-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1.5 1.75 6 6.25l4.5-4.5' stroke='%234a5568' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --ic-sun: none;  --ic-moon: block;
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --ic-sun: block; --ic-moon: none;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* Bara vikterna 400/500/600 finns laddade. Utan detta reset ärver rubriker
   webbläsarens 700 och får syntetisk fetstil som inte matchar resten. */
h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: var(--fw-bold); }

[hidden] { display: none !important; }

html { background: var(--bg); }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

.num { font-variant-numeric: tabular-nums; }
.mono { font-family: var(--mono); }

/* Kvotsiffror är en av exakt tre mono-platser (spec §5) — samma form i
   tabell, underrad, hjälptext och dialog. */
.qnum {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--text-muted);
}
.qnum strong { color: var(--text); font-weight: var(--fw-bold); }

a { color: inherit; text-decoration: none; }

button, input, textarea, select {
  font: inherit;
  color: inherit;
}

/* Ingen border-radius här: fokusmarkeringen är outline + offset, och radien
   vann över komponenternas egna värden så att hörnen hoppade från 10 px till
   6 px i samma ögonblick som kontrollen fick fokus. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Nedtonad TLD — sista punktsegmentet i ett domännamn (spec §5) */
.tld { color: var(--text-faint); font-weight: var(--fw-medium); }

/* ============================================================
   Topplist
   ============================================================ */

.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--bg-topbar);
  border-bottom: 1px solid var(--border);
  /* Pulsen sveper till translateX(250%) och nådde 140 % av viewportbredden:
     dokumentets scrollWidth växte cykliskt och en horisontell rullningslist
     tändes och släcktes var 1,6:e sekund under hela mätningen. Spåret klipps
     i sidled; clip (inte hidden) låter pulsens 1 px utanför underkanten stå
     kvar och gör inte topplisten till en scrollport. */
  overflow-x: clip;
}
.topbar-inner {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 var(--s6);
  height: 56px;
  display: flex;
  align-items: center;
  gap: var(--s6);
}

/* Pulsspåret är alltid närvarande (2 px) så layouten aldrig hoppar. */
.topbar .pulse {
  position: absolute;
  left: 0;
  bottom: -1px;
  height: 2px;
  width: 40%;
  background: linear-gradient(90deg, transparent, var(--accent), var(--accent-hover), var(--accent), transparent);
  opacity: 0;
  pointer-events: none;
}
body.measuring-live .topbar .pulse {
  opacity: 1;
  animation: pulse-sweep 1.6s linear infinite;
}
@keyframes pulse-sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(250%); }
}

.wordmark {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  font-weight: var(--fw-bold);
  letter-spacing: -0.01em;
  white-space: nowrap;
}
.wordmark .mark {
  width: 22px;
  height: 22px;
  border-radius: 7px;
  background: var(--accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.wordmark .mark svg { display: block; }

.topnav {
  display: flex;
  gap: var(--s2);
  flex: 1;
}
.topnav a {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
  padding: 6px 12px;
  border-radius: var(--r-sm);
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.topnav a:hover { color: var(--text); background: var(--bg-elev-2); }
.topnav a[aria-current="page"] { color: var(--text); background: var(--bg-elev-2); }

/* Användarchipet är en meny (enda posten "Logga ut"), inte en länk —
   som länk till inloggningen loggade ett klick ut en mitt i demon. */
.userchip {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  white-space: nowrap;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: 3px 8px 3px 3px;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.userchip:hover { color: var(--text); background: var(--bg-elev-2); }
.userchip[aria-expanded="true"] { color: var(--text); background: var(--bg-elev-2); border-color: var(--border-strong); }
.avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--bg-elev-2);
  border: 1px solid var(--border-strong);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
  letter-spacing: 0.02em;
  flex: none;
}


/* Temaväxlaren och kontochipet hålls ihop: .topbar-inner har gap var(--s6) (32 px), som hade
   läst som två orelaterade kontroller i stället för ett handtag och en identitet.

   Gruppens margin-left: auto ersätter M2-8:s .userchip { margin-left: auto }, som fanns just
   för att prototypens .topnav { flex: 1 } inte byggs i produktionen förrän M3-5/M7-4. Inuti en
   shrink-to-fit-grupp hade den regeln ändå inte haft någon effekt. */
.topbar-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--s2);
}

/* Samma språk som .userchip - genomskinlig i vila, ytan tänds först vid hover. En ikonknapp med
   egen ram eller egen färg hade läst som en åtgärd i topplisten; det här är ett handtag. */
.themetoggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: none;
  color: var(--text-faint);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.themetoggle:hover { color: var(--text); background: var(--bg-elev-2); }
.themetoggle svg { display: block; }

/* Knappen visar det tema den byter TILL, inte det som gäller: i mörkt läge en sol, i ljust en
   måne. Vilken av de två som syns kan inte härledas ur data-theme ensamt - utan attributet gäller
   operativsystemets val - så växlingen hänger på samma tre regler som color-scheme längst upp i
   filen, via --ic-sun/--ic-moon. */
.themetoggle .ic-sun  { display: var(--ic-sun, block); }
.themetoggle .ic-moon { display: var(--ic-moon, none); }

/* ============================================================
   Sidlayout
   ============================================================ */

.page {
  max-width: 1120px;
  margin: 0 auto;
  padding: var(--s6) var(--s6) var(--s8);
}

/* ============================================================
   Knappar
   ============================================================ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 44px;
  padding: 0 18px;
  border: none;
  border-radius: var(--r-md);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.btn-primary { background: var(--accent-btn); color: #ffffff; }
.btn-primary:hover { background: var(--accent-btn-hover); }
/* Spärrad primärknapp ska ligga UNDER ghost-knappen visuellt, inte över:
   i överlåtelsedialogen står "Avbryt" bredvid en blockerad "Överlåt projektet"
   och de två får inte läsa som lika starka (spec §8.2). */
.btn-primary:disabled {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  color: var(--text-faint);
  cursor: default;
}
.btn-ghost {
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--border);
}
.btn-ghost:hover { color: var(--text); border-color: var(--text-faint); }
/* Dämpad ghost — används för "Avbryt" när dialogen har två verkliga val */
.btn-quiet {
  background: transparent;
  color: var(--text-muted);
  border: none;
  height: 34px;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--border-strong);
}
.btn-quiet:hover { color: var(--text); text-decoration-color: var(--text-faint); }
.btn-sm { height: 34px; padding: 0 13px; font-size: var(--fs-sm); border-radius: var(--r-sm); }

/* ============================================================
   Chip (förbehåll, roll, status) — alltid neutralt
   ============================================================ */

.chip {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);          /* spec §3.5 — faint klarar inte AA i 11 px */
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 3px 9px;
  white-space: nowrap;
}
.chip-solid { background: var(--bg-elev-2); border-color: var(--border-strong); color: var(--text); }

/* ============================================================
   Vyhuvuden
   ============================================================ */

.backlink {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  transition: color var(--dur-fast) var(--ease);
}
.backlink:hover { color: var(--text); }

.view-head {
  display: flex;
  align-items: baseline;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-top: var(--s4);
}
/* min-width och overflow-wrap är ett avsteg mot prototypen (se filens huvudkommentar) - de saknas i
   prototype/style.css. Huvudet är flex (.view-head), och utan min-width: 0 kan ett flexbarn aldrig
   krympa under sin innehållsbredd - en lång domän utan bindestreck sprängde annars sidbredden vid
   380 px i stället för att radbrytas. */
.view-head h1 {
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  letter-spacing: -0.02em;
  line-height: 1.1;
  min-width: 0;
  overflow-wrap: anywhere;
}
.view-head .chip { position: relative; top: -6px; }

.view-meta {
  margin-top: var(--s2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.view-meta .sep { color: var(--text-faint); margin: 0 6px; }
/* Mätningschipet bär sin egen gräns via pulsprickan — en middot direkt före
   prickan läser som ett skrivfel, två runda tecken intill varandra. */
/* Pulsprickan är samma komponent här som i projektlistan — samma storlek,
   samma neutrala färg. Pågående mätning är ett tillstånd, och accent betyder
   uteslutande interaktion (spec §6.1); livesignalen bärs av pulslinjen. */
.view-meta .measure { color: var(--text-muted); margin-left: 14px; }

/* Rubrik för listvyerna — 28 px, 36 px är reserverat för domännamnet */
.list-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
}
.list-head h1 {
  font-size: 28px;
  font-weight: var(--fw-bold);
  letter-spacing: -0.015em;
  line-height: 1.2;
}
.list-head .sub {
  margin-top: var(--s2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.list-head .sub .sep { color: var(--text-faint); margin: 0 6px; }

/* Pulserande prick — pågående mätning i listtext */
.dot-pulse {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-muted);
  margin-right: 6px;
  vertical-align: 1px;
  animation: measurepulse 1.6s ease-in-out infinite;
}
@keyframes measurepulse {
  0%, 100% { opacity: 0.25; }
  50%      { opacity: 1; }
}

/* ============================================================
   Beställningsyta (M6-8, F67a/F74/F79)
   ============================================================ */

/* Ingen ny spärrstil - .btn-primary:disabled finns redan (DESIGNSPEC §8.2, M5-4c avvägning 11). */
.orderbar {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-top: var(--s3);
  flex-wrap: wrap;
}
.ordernote {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* ============================================================
   Inmatningsfält + kvot
   ============================================================ */

.addbar {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  margin-top: var(--s5);
}
/* Fältet och raden under det är en kolumn, inte två syskon över hela
   sidbredden: flerradshinten högerställdes tidigare mot sidans kant, hamnade
   under kvotindikatorn och lästes som en del av kvoten i stället för som en
   instruktion för fältet den beskriver. */
.addcol { flex: 1; min-width: 0; }
.addfield { position: relative; }
.addfield textarea {
  display: block;
  width: 100%;
  resize: none;
  /* Auto: fältet växer med innehållet till taket och scrollar därefter.
     Med overflow:hidden gick rader 9+ inte att nå vid stor inklistring. */
  overflow-y: auto;
  max-height: 200px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 11px 14px;
  min-height: 44px;
  line-height: 20px;
  color: var(--text);
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.addfield textarea::placeholder { color: var(--text-faint); }
.addfield textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
}
.addfield textarea:disabled { color: var(--text-faint); cursor: not-allowed; }
/* M4-12: kvoten nådd av ett AVVISAT svar spärrar fältet med readOnly, inte disabled - texten ska
   förbli valbar och kopierbar (se lockQuotaField i seoanalys.js). Samma dämpade läsning som det
   spärrade läget ovan, så de två spärrformerna ser identiska ut trots olika DOM-tillstånd. */
.addfield textarea:read-only { color: var(--text-faint); cursor: not-allowed; }
.addfield.multiline textarea { line-height: 22px; }

/* Når innehållet taket tonas underkanten ut så att avklippet läses
   som "det finns mer" i stället för som en trasig rad. */
.addfield::after {
  content: "";
  position: absolute;
  left: 1px;
  right: 1px;
  bottom: 1px;
  height: 26px;
  border-radius: 0 0 var(--r-md) var(--r-md);
  /* color-mix, inte ett hårdkodat rgba(...,0): det transparenta stoppet måste vara
     SAMMA färg som det ogenomskinliga, annars tonar gradienten mot fel kulör i det
     ena temat. Det gamla värdet var mörkrets --bg-elev-2 skriven för hand.
     "0%" är inte utfyllnad: color-mix utan procentsatser blandar 50/50, och
     gradienten hade då startat på halv opacitet och läst som en kant. */
  background: linear-gradient(to bottom, color-mix(in srgb, var(--bg-elev-2) 0%, transparent), var(--bg-elev-2));
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease);
}
.addfield.scrolling::after { opacity: 1; }

/* Raden under fältet: kvittens till vänster, flerradshint till höger.
   Hinten låg tidigare inne i textarean och täckte sista raden. */
.addfoot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  margin-top: var(--s2);
  min-height: 20px;
}
/* Kvittensen är faint enligt spec §8.3: en överhoppad dubblett är ett neutralt
   utfall och får inte bli den ljusaste texten på ytan. `quotanote` nedan bär
   det stående kvotbeskedet, som inte är övergående. */
.addnote {
  font-size: var(--fs-sm);
  color: var(--text-faint);
}
/* DESIGNSPEC §8.3 väger tyngre än prototypens .notice för det stående
   kvotbeskedet - det är inte övergående och ska därför inte dela nyans
   med kvittensen. */
.addnote.quotanote {
  color: var(--text-muted);
}
.multihint {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.addfoot.multiline .multihint { opacity: 1; }

.quota {
  min-width: 136px;
  padding-top: 3px;
  text-align: right;
}
.quota .label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.quota .bar {
  margin-top: 7px;
  margin-left: auto;
  width: 112px;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
  overflow: hidden;
}
/* Kvotstapeln är tillstånd, inte interaktion — därför neutral, aldrig accent
   (spec §3.4). Fyllnaden är --text-faint: --border-strong gav bara 1,2:1 mot
   spåret och stapeln lästes som ett enfärgat streck. */
.quota .bar i {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--text-faint);
  transition: width var(--dur-slow) var(--ease), background var(--dur) var(--ease);
}
.quota.near .bar i { background: var(--text-muted); }
.quota.full .bar i { background: var(--text); }

/* ============================================================
   Positionskartan — signaturen (spec §8.7)
   ============================================================ */

/* Sidopadding 24 px = tabellradernas padding, så att axelns nollpunkt ligger i
   exakt samma x som kolumnen Sökord. Två identiskt breda kort med olika gutter
   underminerar kartans egen motivering: nya streck ska landa rakt ovanför
   raderna som glider in. */
.pmapcard {
  margin-top: var(--s4);
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 12px var(--s5);
}
.pmaphead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  margin-bottom: 2px;
}
.pmaphead .lbl {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}
/* Hint-texten är kartans enda förklaring och dubbleras ingenstans — muted, inte faint */
.pmaphead .hint { font-size: var(--fs-xs); color: var(--text-muted); }

/* M5-9: samma golv som JS:ens PMAP_MIN_WIDTH (måttet drawPositionMap räknar sin geometri mot) -
   men golvet får aldrig vara BREDARE än värden faktiskt har plats för, annars spränger det
   sidbredden vid 320 px viewport. min() ger 320px när det finns plats, annars 100% av föräldern. */
#pmap { min-width: min(320px, 100%); }
/* M5-9: overflow: visible (SVG:s eget default är overflow: hidden på rotelementet) - träffytorna
   klamras redan innanför viewBox (0 <= x <= W-hitWidth, se buildTickHitArea), men fokusringen
   ritas UTANFÖR sin egen träffyta (outline-offset), och den yttersta ringen klipptes annars bort
   av just den implicita hidden-klippningen på de yttersta strecken. Kortets egen padding (minst
   10 px i alla brytpunkter) är gott om marginal för ringens få pixlar bortom kanten. */
.pmap svg { display: block; width: 100%; overflow: visible; }
/* Axel och avgränsare bar tidigare --border (1,2:1) och försvann på projektor.
   --text-faint vid 0,7 opacitet ger ~3,3:1 — kravet för gränssnittsgrafik. */
.pmap .axis      { stroke: var(--text-faint); stroke-opacity: var(--op-ink-faint); stroke-width: 1; }
.pmap .divider   { stroke: var(--text-faint); stroke-opacity: var(--op-ink-faint); stroke-width: 1; stroke-dasharray: 2 3; }
/* Zonen ska ha en egen yta. --bg-elev-2 mot kortet gav 1,09:1 och försvann
   helt vid skärmdelning; --border-strong ger ~1,5:1 — synlig som en plats,
   fortfarande tyst nog att strecken i den läser som data. */
.pmap .zone      { fill: var(--border-strong); fill-opacity: 0.7; }
.pmap .axislabel { font-family: var(--mono); font-size: 10px; fill: var(--text-muted); }
.pmap .tick      { fill: var(--text); fill-opacity: 0.85; }
.pmap .tick-over { fill: var(--text-faint); fill-opacity: 1; }
/* Bortfiltrerat sökord: strecket blir kvar för helhetsbilden men dämpas.
   0,22 gav 1,9:1 — under 3:1 för grafik som bär information, och strecken är
   fortfarande klickbara kontroller som ska gå att hitta. 0,38 ≈ 3:1. */
/* Fyllnaden, inte bara alfat: regeln slår över .tick-over (0,3,0 mot 0,2,0), så ett
   bortfiltrerat >50-streck dämpades från --text-faint OVANPÅ zonens egen platta och
   hamnade på 1,7:1. --text har marginalen som krävs för 3:1 på den svåraste ytan. */
.pmap .hit.off .tick { fill: var(--text); fill-opacity: var(--op-ink-off); }
.pmap .hit       { cursor: pointer; }
.pmap .hit .hitarea { fill: transparent; }
.pmap .hit:hover .tick,
.pmap .hit:focus .tick { fill-opacity: 1; }
.pmap .hit:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* M5-9: --accent mot zonPLATTAN (--border-strong vid 0,7 opacitet ovanpå kortets --bg-elev) ger
   bara ~2,90:1 - under Designgrund avsnitt 8:s 3:1 för en fokusmarkering. --accent-hover (samma
   ton, ljusare) ger ~3,83:1 mot samma komposit och används bara HÄR, inuti zonen - resten av
   kartans streck står kvar direkt mot kortets egen bakgrund, där --accent redan räcker. */
.pmap .hit:has(.tick-over):focus-visible { outline-color: var(--accent-hover); }
.pmap .tick-enter { animation: tickin var(--dur-slow) var(--ease) both; }
@keyframes tickin { from { opacity: 0; } to { opacity: 1; } }
.pmapsvg:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--r-sm); }

/* Miniversion i projektlistan — ren grafik, ingen interaktion. pointer-events: none är ett avsteg
   mot prototypens .pmap-mini svg (M5-8b, fjärde avsiktliga avsteget - se filens huvudkommentar):
   DESIGNSPEC §8.7 kräver att kartan varken fångar fokus eller pekare, så ett klick på kartan
   bubblar till radens ankare i stället för att sväljas av svg:n. */
.pmap-mini svg { width: 180px; height: 32px; pointer-events: none; }
.pmap-mini .axis, .pmap-mini .divider { stroke-opacity: var(--op-ink-faint); }
.pmap-mini .bin { fill: var(--text); fill-opacity: var(--op-ink-faint); }
.pmap-mini .bin.tick-over { fill: var(--text-faint); fill-opacity: 1; }

/* ============================================================
   Tabellkort (sökord och användare delar form)
   ============================================================ */

.tablecard {
  margin-top: var(--s3);
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}
/* Utan positionskarta ovanför (tomt projekt) tar tabellkortet dess luft */
#pmapcard[hidden] + .tablecard,
.admtable { margin-top: var(--s5); }

.tabletools {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s4) var(--s5);
}
.tabletools .count {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.tabletools .filter { position: relative; }
.tabletools .filter input {
  width: 220px;
  height: 32px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 0 10px 0 30px;
  font-size: var(--fs-sm);
  color: var(--text);
  transition: border-color var(--dur-fast) var(--ease);
}
.tabletools .filter input::placeholder { color: var(--text-faint); }
.tabletools .filter input:focus { outline: none; border-color: var(--accent); }
.tabletools .filter > svg {
  position: absolute;
  left: 9px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-faint);
  pointer-events: none;
}

/* Krysset som slutar filtrera. Ligger inne i fältet och tar plats först när
   det finns något att rensa — söktypens inbyggda kryss ser olika ut i olika
   webbläsare och döljs helt i vissa, så vi ritar ett eget. */
.tabletools .filter input { padding-right: 28px; }
.tabletools .filter input::-webkit-search-cancel-button { display: none; }
.filterclear {
  position: absolute;
  right: 5px;
  top: 50%;
  transform: translateY(-50%);
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border: 0;
  padding: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-faint);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.filterclear:hover { color: var(--text); background: var(--bg-elev-2); }
.filterclear:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Notis om att nyss tillagda sökord visas trots filtret. */
.exemptnote {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  white-space: nowrap;
}

/* Ett undantaget sökord markeras diskret i vänsterkanten så att det går att se
   varför raden ligger kvar när filtret säger något annat. */
.row.exempt, tr.exempt, .exempt > .row { box-shadow: inset 2px 0 0 var(--accent); }

/* M5-4c (§0): kolumnuppsättningen omlagd - sökord, sökvolym, position, förändring, rankande URL,
   mät om, åtgärd. Trend-/7-dagarsspåren är rivna, inte bara osynliggjorda. M5-4b, avvägning 1: en
   sjunde spårbredd (36px) för åtgärdscellen läggs sist i ALLA fyra --cols-deklarationer (den här
   och de tre i @media-blocken nedan) - åtgärdskolumnen får INTE försvinna under samma brytpunkt
   som döljer url-kolumnen, F30 måste gå att nå även på en smal skärm. */
.kwtable { --cols: minmax(220px, 1.5fr) 88px 96px 96px minmax(190px, 1fr) 88px 36px; }

.thead {
  display: grid;
  grid-template-columns: var(--cols);
  gap: var(--s4);
  align-items: center;
  padding: 0 var(--s5);
  height: 36px;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: var(--topbar-h);
  background: var(--bg-elev);
  z-index: 4;
}
.thead > * {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  white-space: nowrap;
  /* Ingen rubrik får spilla ut ur kortet: utan detta sköt "7 DAGAR ↕" ut över
     kortkanten på smala skärmar och sorteringsglyfen kapades av kantlinjen.
     `clip` med 4 px marginal och inte `hidden`: cellen ska inte bli en
     scrollport, och fokusringen (2 px med 2 px offset) måste få plats. */
  min-width: 0;
  overflow: clip;
  overflow-clip-margin: 4px;
}
/* Kort rubrikform för smala skärmar — bara en av de två är i DOM-flödet, så
   skärmläsaren läser alltid exakt en etikett. */
.lbl-short { display: none; }
.thead .sortable {
  background: none;
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0;
  text-transform: inherit;
  letter-spacing: inherit;
  font-size: inherit;
  font-weight: inherit;
  color: inherit;
  transition: color var(--dur-fast) var(--ease);
}
.thead .sortable:hover { color: var(--text); }
/* Sorteringsglyfen är ↑/↓ — ▲/▼ betyder positionsrörelse i samma tabell och
   får inte betyda två saker. Pilen visas alltid, så sorterbara rubriker går
   att skilja från icke-sorterbara. */
.thead .sortable .arrow {
  font-size: 10px;
  color: var(--text-faint);
  transition: color var(--dur-fast) var(--ease);
}
.thead .sortable[data-dir] { color: var(--text); }
.thead .sortable[data-dir] .arrow { color: var(--text); }
.th-right { text-align: right; }
.th-right .sortable { justify-content: flex-end; width: 100%; }
/* M7-9, städning: kostnadskolumnernas långa rubrikform ("Senaste helmånad · september") får bryta
   rad i stället för att ärva .thead:s nowrap - "september" är det längsta svenska månadsnamnet,
   och vid full skrivbordsbredd (ingen brytpunkt aktiv, se §7:s 990/815-block) rymdes den inte på
   en rad i kolumnens 188/148 px utan att texten faktiskt sköt ut ur sitt spår - `overflow: clip`
   på .thead > * döljer bara det visuellt, etikettens EGNA rektangel (vad ett tillgänglighetsträd
   eller en pekare mäter) förblev bredare än cellen. */
.th-cost .lbl-long {
  white-space: normal;
  text-align: right;
}

/* Ligger rubrikraden överst i kortet bär kortets egen kant linjen */
.tablecard > .grid:first-child > .thead:first-child {
  border-top: none;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}

/* Text som bara finns för skärmläsare */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* M5-4c: inset box-shadow i stället för border-top - en riktig border LÄGGER TILL 1 px på en
   auto-höjd box (border-box krymper bara ett EXPLICIT height/width, inte en innehållsstyrd
   höjd), så den andra och alla följande rader blev 1 px högre än den första. Det gjorde
   AMeasuringRow_AndALandedRow_HaveTheExactSameHeight flakigt beroende på vilken av de två rader
   som råkade hamna sist i DOM-ordningen. Samma visuella linje, ingen bidragande höjd. */
.kw + .kw .row { box-shadow: inset 0 1px 0 var(--border); }

.row {
  display: grid;
  grid-template-columns: var(--cols);
  gap: var(--s4);
  align-items: center;
  min-height: var(--row-h);
  padding: 5px var(--s5);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.row:hover { background: var(--bg-elev-2); }
.kw .row:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }
.kw.open .row { background: var(--bg-elev-2); }
.kw:last-child .row { border-radius: 0 0 var(--r-lg) var(--r-lg); }
.kw:last-child.open .row { border-radius: 0; }

/* Engångspuls när ett streck i positionskartan klickas. Den startar först när
   scrollningen är klar (se revealRow) och varar 1,8 s, så att markeringen
   fortfarande syns när ögat hinner följa efter från kartan till raden. */
.kw.mappulse .row { animation: rowpulse 1800ms ease-out; }
@keyframes rowpulse {
  from { background-color: var(--accent-dim); }
  to   { background-color: transparent; }
}

.cell-kw {
  font-weight: var(--fw-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cell-kw .newtag {
  margin-left: 8px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--text-faint);
  letter-spacing: 0.04em;
}

/* M5-4c, avvägning 4: cell-pos blir en flexkolumn så mätdatumet (.measured-on) får plats UNDER
   siffran utan att bryta .poswrap (gapmark-ringen och pos-pulse-effekten är opåverkade - de
   positioneras relativt .poswrap, inte relativt cell-pos). Höjden är innehållsstyrd (§8.12 sätter
   en minsta radhöjd, inte en fast) - poswrap och measured-on har fasta egna mått, så mätläget och
   det landade läget blir exakt lika höga ändå, utan att overflow behöver kapa något.  */
.cell-pos {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  text-align: right;
  font-size: var(--fs-pos);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.015em;
  line-height: 1;
}
/* Höjden är satt explicit och delad med .shimmer (24px): typsnittets egna radboxmått för siffertext
   kan avvika någon tiondels pixel från skimrets, och utan ett gemensamt tak hoppade raden i höjdled
   när värdet landade trots identisk padding. overflow är medvetet INTE hidden här - en så liten
   avvikelse får svämma ut ett par tiondelar utanför boxen hellre än att klippas (och gapmark-ringen
   och pos-pulse-effekten, som sträcker sig utanför poswrap, förblir opåverkade). */
.cell-pos .poswrap {
  position: relative;
  display: inline-block;
  padding: 2px 4px;
  height: 24px;
  line-height: 24px;
  flex-shrink: 0;
  /* M5-9: gapmark-ringen + ">50" är EN visuell enhet (ringen står FÖRE talet, aldrig under det) -
     utan nowrap bröts paret till två rader på en smal karta (~760 px) och drog upp radhöjden
     48 → 63 px, trots att poswrap:s egen höjd är fast. */
  white-space: nowrap;
}
/* Mätdatumet (§0/F67b): alltid NÄRVARANDE, även tomt i mätläget - §8.4 förbjuder att raden hoppar
   i höjdled när värdet landar. En TOM blocknivå-box kollapsar till 0 px oavsett line-height (ingen
   text ⇒ ingen "strut") - explicit height (inte bara line-height) är därför det som faktiskt
   reserverar platsen, bevisat av AMeasuringRow_AndALandedRow_HaveTheExactSameHeight. */
.cell-pos .measured-on {
  display: block;
  max-width: 100%;
  height: 14px;
  flex-shrink: 0;
  font-size: var(--fs-xs);
  font-weight: var(--fw-normal);
  font-variant-numeric: normal;
  letter-spacing: normal;
  line-height: 14px;
  color: var(--text-faint);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cell-pos .over50 { color: var(--text-faint); font-weight: var(--fw-medium); }
.cell-pos .failed {
  color: var(--text-faint);
  font-weight: var(--fw-normal);
  cursor: help;
}
/* Dagens mätning uteblev men det rapporterade medelvärdet står kvar — den
   uteblivna dagen markeras med en liten ihålig ring FÖRE talet. I upphöjt läge
   till höger lästes raden som "34°", alltså som en enhet eller ett tryckfel,
   och positionssiffran är den enda siffran som vänds mot prospektet. */
.cell-pos .gapmark {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 7px;
  border-radius: 50%;
  border: 1px solid var(--text-faint);
  vertical-align: 0.3em;
  cursor: help;
}

/* ============================================================
   Konkurrenttabellen (M9-4, DESIGNSPEC §8.13/§9.3 punkt 6) — samma tabellstomme som
   sökordstabellen (.thead/.row/.cell-* / gap-formen ovan är redan generiska och återanvänds rakt av,
   se t.ex. .row:hover nedan), bara det konkurrentspecifika läggs till här.
   ============================================================ */

/* --comp-count räknar bara de FAKTISKA konkurrenterna (satt av updateCompetitorTable i
   seoanalys.js) — "Din position" har sitt eget spår före dem, precis som §8.13:s
   kolumnuppställning "Sökord | Din position | Konkurrent 1 … Konkurrent n". Egen-kolumnen sizas
   som konkurrenternas: också DESS rubrik är ett domännamn, och ett klippt "M9…" är lika oläsbart
   där som i en konkurrentkolumn. margin-top skriver
   ÖVER .tablecard:s var(--s3) - konkurrentkortet ska ha samma luft mot sökordskortet som
   positionskartan har mot SITT tabellkort (§8.13 ordagrant).

   Sökordsspåret har ett TAK (360px, ~48 tecken innan ellipsis) och positionsspåren - egen kolumn
   OCH konkurrenterna - delar resten LIKA (1fr var). Tidigare var sökordsspåret 1.5fr och spåren
   max-content, vilket lade hela överskottsbredden mellan sökordet och första siffran (ett tomt fält
   på 400-600 px rakt genom raden) OCH gav kolumnerna olika bredd beroende på hur långt domännamnet
   råkade vara - två kolumner som ska jämföras med varandra får inte se olika viktiga ut.

   Lika breda spår ersätter också det avslutande spacerspår som fanns här en kort stund: när spåren
   delar hela bredden finns inget överskott kvar att parkera, och raderna (subgrid-barn spända
   1 / -1) täcker kortbredden av sig själva. Golvet 96px är kvar för det smala fallet; över det
   växer alla positionsspår i takt. Spåren rullar fortfarande i .gridscroll när fem långa rubriker
   inte ryms (§7). */
.comptable { margin-top: var(--s4); --cols: minmax(220px, 360px) minmax(96px, 1fr) repeat(var(--comp-count), minmax(96px, 1fr)); }

/* M9-5: .comphead bär numera BÅDE rubriken och inmatningsraden - flex-wrap (till skillnad från
   M9-4:s enda-rubrik-version) så att #compinput/#compaddbtn faller till en egen rad i stället för
   att tvinga fram sidbredd på en smal skärm (§7, NarrowViewport_TheInputRow_...).

   Kortets huvud är numera en VERKTYGSRAD med samma grammatik som sökordskortets .tabletools:
   etiketten till vänster, kontrollen till höger, allt på en rad. Tidigare staplades rubrik,
   fullbreddsfält, knapp och chips som fyra block i fyra olika bredder - ingen av dem delade kant
   eller axel med någon annan, och det är den uteblivna rytmen (inte någon enskild detalj) som
   läste som opolerat bredvid sökordskortet. */
.comphead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2) var(--s4);
  padding: var(--s4) var(--s5);
}
/* Rubriken och inledningsmeningen som en enhet i verktygsradens vänsterkant. min-width: 0 så att
   meningen kan krympa i stället för att knuffa ut inmatningsraden ur raden. */
.comptitle {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  min-width: 0;
}
/* Skriver över .comptable .hint:s egen kortpadding - inne i verktygsraden bär .comphead luften. */
.comptitle .hint { padding: 0; }
.comphead .lbl {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}
/* Inmatningsraden (§8.13): står numera till HÖGER på rubrikens rad (margin-left: auto), inte som
   en egen fullbreddsrad under den - beställarbeslut 2026-09-08. Fältet och knappen delar rad,
   knappen sist. order: 3 står kvar: den håller inmatningsraden mellan .comptitle (order 0) och
   notisytorna (order 5/6) oavsett källordning, och FrontendAssetTests läser den literalt.
   flex-wrap kvar av samma skäl som förut - på en smal skärm faller knappen under fältet i stället
   för att tvinga fram sidbredd (§7, NarrowViewport_TheInputRow_...). */
.compaddrow {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--s2);
  flex: 0 1 auto;
  margin-left: auto;
  order: 3;
}
/* Måtten är sökordskortets filterfält (.tabletools .filter input): 220px brett, --bg som yta,
   --fs-sm - och .btn-sm:s 34px i höjd, så fältet och knappen är lika höga. Det fullbreddsfält som
   stod här förut var kortets största yta och gav åtgärden mer tyngd än tabellen den ska mata.
   flex: 1 1 160px + max-width: 100% låter det ändå krympa och till sist ta hela raden på en smal
   skärm (§7) - width är bara önskemålet, basen är golvet. */
#compinput {
  flex: 1 1 160px;
  width: 220px;
  max-width: 100%;
  min-width: 0;
  height: 34px;
  padding: 0 var(--s3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-sm);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
/* Samma fokusform som filterfältet och dialogernas fält - --accent-dim, aldrig en egen ton. */
#compinput:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
#compinput:disabled { color: var(--text-faint); cursor: not-allowed; }
#compinput::placeholder { color: var(--text-faint); }
/* Neutral kant, aldrig --down: rött betyder nedåtgående rörelse i den här produkten och ingenting
   annat (DESIGNSPEC §6.1, "Designgrund princip 1, absolut"). Ett fält och en förändringspil som
   bär samma röda ton på samma sida ger färgen två motstridiga betydelser. */
#compinput[aria-invalid="true"] { border-color: var(--border-strong); }
/* #compnormalized (förhandsvisningen, samma form som dialogens #normalized) tar en EGEN rad under
   verktygsraden (order: 5, flex-basis: 100%) och står högerställd, så att den hänger under det fält
   den beskriver. Tidigare delade den rubrikens rad - den platsen är nu inmatningsradens, och en
   förhandsvisning som växer i sidled hade knuffat fältet i sidled medan man skriver i det. */
#compnormalized {
  order: 5;
  flex: 1 1 100%;
  min-width: 0;
  padding: 0;
  text-align: right;
}
/* Utformningen delas med dialogens .normalized.problem längre ner - se den regeln. Här står bara
   det som skiljer: förhandsvisningen är högerställd i konkurrentkortet. */
#compnormalized.problem { justify-content: flex-end; }
.comptable .hint {
  padding: 4px var(--s5) 0;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
/* #complimitnote/#compgapnote - samma dämpade notisform, ovanför rutnätet. Luften ligger UNDER
   raden i stället för över den: verktygsraden bär numera sin egen bottenpadding. */
.compnote {
  padding: 0 var(--s5) var(--s3);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.compnote button { margin-left: 6px; }
/* #compactionnote (M9-5): samma klientägda notisform som .ordernote - tom och osynlig (ingen
   bakgrund/kant) tills klienten skriver ett POST-besked. order: 6, EFTER inmatningsraden
   (.compaddrow, order: 3) och förhandsvisningen (order: 5): utan en egen order faller den till
   flexboxens standardvärde 0 och landar då mellan rubriken och inmatningsraden i källordning i
   stället för som en egen notisrad under kortets huvud. Högerställd av samma skäl som
   #compnormalized - kvittot hör till knappen som utlöste det. */
.compactionnote {
  order: 6;
  flex-basis: 100%;
  padding: 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  min-height: 0;
}
/* Chipsen (#compchips): en radbrytande rad av bevakade konkurrenter under inmatningsraden.
   flex-wrap är hela poängen på en smal skärm - en enda lång rad av domännamn hade själv tvingat
   fram sidbredd (§7). Raden ligger direkt ovanför rubrikradens linje och läses därmed som
   tabellens LEGEND, inte som ett spill efter formuläret - samma relation som .tabletools har till
   .thead i sökordskortet. Fem långa domäner bryter till två rader och kortet växer nedåt; inget
   annat flyttar, vilket är hela skälet till att chipsen inte delar rad med inmatningsfältet. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  padding: 0 var(--s5) var(--s4);
  max-width: 100%;
}
/* Namngivningen delar ".chip" med det generiska förbehålls-/status-chipet (§3.5) - ett
   konkurrentchip är ett annat mönster (bär ett domännamn, kan bli brett, aldrig VERSALER) och
   behöver därför skriva om VARJE property det generiska .chip sätter, inte bara lägga till egna.
   .chips .chip (två klasser i kedjan) väger tyngre än den ensamma .chip-regeln ovan utan !important.
   overflow-wrap/min-width: 0 är det som faktiskt löser §7: ett domännamn utan blanksteg
   ("superlangkonkurrentdomain.se") bryts annars aldrig och sprängde sidbredden vid 380px
   (NarrowViewport_TheInputRow_...). */
.chips .chip {
  display: inline-flex;
  align-items: center;
  gap: 0;
  max-width: 100%;
  min-width: 0;
  padding: 3px 4px 3px 10px;
  border-radius: var(--r-sm);
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-normal);
  text-transform: none;
  letter-spacing: normal;
  white-space: normal;
  overflow-wrap: anywhere;
}
/* Samma dämpade TLD som kolumnrubriken och projektvyns h1 - chipet och rubriken pekar på samma
   domän och ska sättas likadant. Radien är --r-sm, inte 999px: kortets övriga element (fält, knapp,
   notiser) har alla samma hörn, och en pill mitt bland dem läste som ett främmande mönster. */
.chips .chip .tld { color: var(--text-faint); }
/* width/height: 24px (inte de tidigare 18px) - WCAG 2.2 2.5.8 kräver en träffyta på minst 24×24
   CSS-pixlar för ett pekmål som inte är en del av ett meningsflöde. margin-left ersätter chipets
   forna gap: 4px - det gapet hamnade också mellan namnet och TLD:n och delade domänen i två. */
.chips .chip .chipclose {
  margin-left: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 24px;
  height: 24px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  font-size: var(--fs-sm);
  line-height: 1;
  cursor: pointer;
}
.chips .chip .chipclose:hover,
.chips .chip .chipclose:focus-visible { background: var(--bg-elev); }
.chip .chipclose:disabled { opacity: 0.5; cursor: not-allowed; }

/* Radernas kolumnantal varierar (--comp-count, 1–5) - en fast bredd per breakpoint hade antingen
   spräckt sidan vid fem konkurrenter eller slösat plats vid en. Rutnätet rullar i sitt EGET kort i
   stället (§7: kortet får aldrig tvinga fram sidbredd) - se NarrowViewport_WithFiveCompetitors_
   NeverForcesPageWidth. Rullningen sitter på .gridscroll (omslaget), inte på .grid självt: en
   gridbehållare med overflow-x på sig själv sizas fortfarande som en vanlig blockbox (width: auto
   = fyll föräldern), så minmax(96px, max-content)-spåren föll till sitt 96px-golv i stället för
   att svälla ut i rullningsytan. .grid får därför width: max-content i stället - se nedan. */
.comptable .gridscroll {
  overflow-x: auto;
}
/* Kortet självt är den ENDA gridbehållaren; rubrikraden, rowgroupen och varje datarad ärver dess
   spår med subgrid. Tidigare bar varje rad en egen kolumnmall, och då MÅSTE varje spår vara
   innehållsoberoende för att rubrikens och radernas kolumnkanter ska falla ihop - därav det fasta
   96px-spåret. Men 96px är dimensionerat efter CELLINNEHÅLLET (1-2 siffror, ett tankstreck, en
   prick), medan det enda breda i kolumnen är RUBRIKEN: två konkurrenter med gemensam början
   renderades båda som "SUPER…" och blev omöjliga att skilja åt i den tabell vars hela syfte är att
   jämföra dem. Med subgrid kan spåret sizas av sitt bredaste innehåll utan att något kan glida
   isär - kolumnkanterna faller ihop av konstruktion i stället för av disciplin.
   width: max-content läter kortets bredd följa spårens sammanlagda max-content-bredd i stället för
   att stanna vid föräldrabredden; min-width: 100% hindrar den från att krympa under kortet när
   innehållet är smalare (färre eller kortare konkurrentnamn).
   Radernas padding äter av första och sista spåret, men lika mycket på varje rad, så
   inbördesjusteringen står. Rutnätet rullar i sitt EGET kort när fem långa namn inte ryms
   (§7: kortet får aldrig tvinga fram sidbredd) - se NarrowViewport_WithFiveCompetitors_
   NeverForcesPageWidth. */
/* column-gap: 0 (sökordstabellen har var(--s4)). Luften ligger i stället som padding INUTI varje
   positionscell, så att den lodräta avgränsaren nedan kan sitta exakt på kolumngränsen. Med ett gap
   hade linjen hamnat vid cellens vänsterkant med 16 px tomrum före sig och lästs som en linje som
   tillhör den ena kolumnen i stället för som gränsen mellan två. */
.comptable .grid {
  display: grid;
  grid-template-columns: var(--cols);
  column-gap: 0;
  width: max-content;
  min-width: 100%;
}
.comptable .thead,
.comptable #complist,
#complist .comprow {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  column-gap: 0;
}
/* Sökordet behöver sin egen luft mot första avgränsaren när gapet är borta. */
.comptable .cell-kw { padding-right: var(--s4); }
/* align-items: stretch (i stället för sökordstabellens center) på rubrikraden och varje datarad:
   den lodräta avgränsaren ritas som en inset box-shadow på CELLEN, och en centrerad cell är lägre
   än sin rad - linjen blev då en serie streck med ett hål vid varje radgräns i stället för en obruten
   kolumnkant. Cellerna centrerar i stället sitt innehåll själva, med flex. */
.comptable .thead,
#complist .comprow { align-items: stretch; }
.comptable .cell-kw {
  display: flex;
  align-items: center;
}
/* Två role="grid"-landmärken på samma sida kan inte båda vara sticky mot samma --topbar-h utan
   att sökordsrubriken och konkurrentrubriken börjar överlappa när båda korten rullas i vy
   samtidigt - konkurrentkortet är kortare (högst fem rader, F81) och behöver ingen egen
   sticky-rubrik för att förbli läsbar. */
.comptable .thead { position: static; }
.comptable .row { cursor: default; }
/* Radseparator och nedre hörnradie - samma mönster som .kw + .kw .row/.kw:last-child .row, men
   utan .kw-omslaget (konkurrentraderna ligger direkt i #complist, se planens markupspec). */
#complist .comprow + .comprow { box-shadow: inset 0 1px 0 var(--border); }
#complist .comprow:last-child { border-radius: 0 0 var(--r-lg) var(--r-lg); }
/* Samma hover-yta som sökordstabellens .row:hover (§6.2), men satt på CELLERNA (.cell-comp) -
   background ärvs inte, och testet läser getComputedStyle på cellen, inte på raden. Tonen var
   tidigare ett problem för ledarmarkeringen, som bar samma --bg-elev-2 och därför försvann i den
   hovrade raden; sedan markeringen flyttat till ett eget .lead-spann med solid --text-fyllning
   finns den konflikten inte kvar - hovringen färgar cellen, pillen ligger ovanpå den. */
#complist .comprow:hover .cell-comp { background: var(--bg-elev-2); }

/* Värdet står CENTRERAT i sitt spår (beställarbeslut 2026-09-09), inte högerställt. Med lika breda
   spår och en lodrät avgränsare per kolumn är mitten den plats som entydigt hör till kolumnen; en
   högerkant hör lika gärna till avgränsaren efter den. Rubriken centreras på samma sätt (se
   .th-comp nedan), så tal och domännamn står över varandra.

   box-shadow, inte border-left: en riktig border lägger till 1 px på en innehållsstyrd box och gör
   varje rad utom den första en pixel högre - samma felmod som .kw + .kw .row redan är kommenterad
   för. Linjen ritas på VARJE cell i kolumnen (rubrikcellen inräknad) och blir därmed obruten nedåt
   tabellen av konstruktion, utan ett eget element att hålla i synk med kolumnantalet. */
.cell-comp {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-variant-numeric: tabular-nums;
  padding: 0 var(--s3);
  box-shadow: inset 1px 0 0 var(--border);
}
/* Egen position är radens referensvärde och sätts något tyngre än konkurrenternas (400) - en andra
   bärare för "det här är du" vid sidan av rubrikens dämpade "du", utan att färg blandas in (§6.2). */
#complist .comprow .cell-comp:first-of-type { font-weight: var(--fw-medium); }
/* Ledarmarkeringen är en BLÄCKPILL runt talet (beställarbeslut 2026-09-08), inte längre en ring
   runt cellen. Den gamla formen var literalt #compinput:s recept - --bg-elev-2 som fyllning, 1px
   kant, --r-sm - satt tio rader från fältet i samma kort, och lästes därför som ett tomt
   inmatningsfält i stället för som en utmärkelse. Det var inte en tolkningsfråga utan en identisk
   form.

   Dubbelkodningen (§6.2) står kvar och blir starkare: formen (solid pill), vikten (--fw-bold) och
   meningen i cellens aria-label (COMPETITOR_LEADER_LABEL i seoanalys.js). Ingen ▲-glyf - §8.12
   reserverar den för positionsrörelse i samma vy. Färgen är --text, alltså NEUTRAL: §6.1:s
   signalfärger får inte låna ut sin betydelse till en utmärkelse.

   Kontrast: --bg-elev på --text ger 15,8:1 i ljust tema och ~14,5:1 i mörkt - långt över kravet i
   båda, och i gråskala är pillen exakt lika tydlig som i färg.

   Pillen sitter på SPANNET, inte på cellen, och är därmed hover-oberoende av konstruktion: den yta
   .comprow:hover färgar om är cellen, och pillen ligger ovanpå den. Den gamla ringen fanns bara
   för att fyllningen delade ton med hovringen - det problemet är borta, inte löst en gång till. */
.cell-comp .lead {
  display: inline-block;
  min-width: 24px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--text);
  color: var(--bg-elev);
  font-weight: var(--fw-bold);
  line-height: 18px;
  text-align: center;
}
/* align-items: stretch på .thead (se ovan) gör varje rubrikcell lika hög som raden, vilket är det
   som håller avgränsaren obruten - men en sträckt cell lägger sitt innehåll i ÖVERKANTEN. Alla
   rubrikceller måste därför centrera själva, inte bara domänrubrikerna: frasrubriken ("Sökord")
   byggs som en klasslös <div role="columnheader"> (buildPhraseHeaderCell) och fångas inte av
   .th-comp, så den hamnade en bit över domännamnen på samma rad. */
.comptable .thead > * {
  display: flex;
  align-items: center;
}
/* Domänrubriken bär dessutom samma luft och samma avgränsare som cellerna under sig - det är de två
   tillsammans som gör linjen obruten från rubrikraden ned genom sista raden. */
.comptable .th-comp {
  padding: 0 var(--s3);
  box-shadow: inset 1px 0 0 var(--border);
}
/* Kolumnspåret (minmax(96px, 1fr) ovan) delar bredden lika med sina grannar, så en rubrik klipps
   inte i det vanliga fallet - rutnätet rullar redan i sitt eget kort (.comptable .grid). max-width
   är bara ett skydd mot ett orimligt långt domännamn: utan det skulle EN sådan rubrik kunna sträcka
   ut alla kolumner. Under det taket är två olika domäner som ser likadana ut ett FEL, inte ett
   kantfall. tld/pilen behåller sin fulla bredd (flex-shrink: 0 nedan). */
.th-comp .dname {
  color: var(--text);
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.th-comp .tld { color: var(--text-faint); flex-shrink: 0; }
/* Rubriken centreras i sitt spår, som värdena under den. .th-right (som rubrikcellen fortfarande
   bär, för utskriftens och stapelvyns skull) högerställer annars knappen. */
.comptable .th-comp .sortable,
.comptable .th-comp .thlabel { justify-content: center; width: 100%; }

/* ── Stapelvyn (DESIGNSPEC §8.13, beställarbeslut 2026-09-03) ──────────────────────────────────
   Under 700 px ryms ingen kolumn per konkurrent. Med F81:s tre konkurrenter behöver tabellen upp
   till 220 + 220x4 = 1100 px, och en telefon ska inte mötas av ett rutnät som måste rullas i
   sidled för att kunna läsas - jämförelsen är hela poängen med vyn, och en jämförelse man rullar
   fram är ingen jämförelse på ett ögonkast.

   Varje sökord blir en grupp: sökordet och egen position som huvudrad, konkurrenterna indragna
   under med sitt domännamn. Brytpunkten är FAST och inte innehållsberoende - en tabell som slår om
   beroende på hur långa domännamnen råkar vara går inte att låsa med ett test, och stapelvyn ska
   vara samma vy varje gång man öppnar telefonen. */
@media (max-width: 699px) {
  /* Rubrikraden döljs VISUELLT, inte med display:none. Skillnaden är tillgänglighetsträdet: en
     display:none-rubrikrad tar med sig columnheader-elementen OCH sorteringsknapparna i dem, och
     då tappar varje cell både sin kolumnkoppling och sin enda sorteringsväg (F88 gäller lika mycket
     här som i tabelläget). Den ligger därför kvar i dokumentflödet, klippt till en pixel, och
     knapparna förblir tabbara: samma knappar som i tabelläget bär sorteringen, bara osynliggjorda
     (DESIGNSPEC §8.13). */
  .comptable .thead {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* Ett tabbat fokus i den klippta rutan ovan skulle annars vara osynligt för en seende
     tangentbordsanvändare (WCAG 2.4.7). Samma grepp som en skip-link: när fokus hamnar i
     rubrikraden återställs den till en kompakt, synlig rad i stället för att förbli klippt. */
  .comptable .thead:focus-within {
    position: static;
    width: auto;
    height: auto;
    margin: 0 0 var(--s3);
    padding: var(--s3) var(--s4);
    overflow: visible;
    clip-path: none;
    white-space: normal;
    display: flex;
    flex-wrap: wrap;
    gap: var(--s3);
    background: var(--bg-elev-2);
    border-radius: var(--r-md);
  }

  /* Ingen rullning och inget rutnät: gridspåren ersätts av vanligt blockflöde, så kortet slutar
     vara bredare än sin förälder. width/min-width nollställs - annars står max-content kvar och
     kortet fortsätter tvinga fram rullning i en vy som inte längre har något brett innehåll. */
  .comptable .gridscroll { overflow-x: visible; }
  .comptable .grid,
  .comptable #complist,
  #complist .comprow {
    display: block;
    width: auto;
    min-width: 0;
  }

  #complist .comprow { padding: var(--s3) var(--s5); }

  /* Sökordet är gruppens rubrik och bär huvudradens tyngd. */
  #complist .comprow .cell-kw {
    display: block;
    font-weight: var(--fw-medium);
    padding: 0 0 2px;
  }

  /* Egen position + konkurrenterna: en rad var, namnet till vänster och talet till höger, med
     samma tabulära siffror som i tabellen så att talen står i lodrät linje under varandra. */
  .cell-comp {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s4);
    padding: 2px 0;
    text-align: right;
    box-shadow: none;
  }
  /* Tabelläget centrerar talet i sitt spår mellan två lodräta avgränsare. Här finns varken spår
     eller avgränsare: varje konkurrent är en egen rad med namnet till vänster och talet till höger,
     så både centreringen och linjen nollställs (box-shadow ovan, text-align: right). */
  /* Namnet ur data-domain, som ::before - aldrig som ett barnelement. Ett barn hade ändrat cellens
     textContent, och flera tester jämför den mot sökordslistans siffra för samma sökord (F88:s
     hårdaste krav: de får aldrig kunna säga olika saker). Se buildCompetitorCell. */
  .cell-comp::before {
    content: attr(data-domain);
    text-align: left;
    color: var(--text-muted);
    font-variant-numeric: normal;
  }

  /* Egen position står först i gruppen, direkt under sökordet - samma ordning som kolumnerna hade
     (F88: egen domän först). Indraget skiljer konkurrenterna från den, så att gruppens ägare går
     att se utan att läsa namnen. */
  .cell-comp:not(:first-of-type) { padding-left: var(--s4); }

  /* Ledarmarkeringen följer med (§8.13) utan en enda egen regel: pillen sitter på .lead-spannet
     runt talet och ser likadan ut i båda vyerna. De tre reglerna som stod här sträckte ut den gamla
     cellringen till radens bredd - en ring som inte längre finns. */

  /* Fältet får ta hela sin rad när verktygsraden brutit isär (knappen faller under av sig själv,
     .compaddrow behåller sitt flex-wrap). */
  #compinput { flex: 1 1 160px; width: auto; }
}
/* Rubrikradens versaler och spärrning (.thead > *) gäller ETIKETTER. Ett domännamn är en
   identifierare: "SUPERLANGKONKURRENTDOMAIN-ETT.SE" är ~20 % bredare än samma namn i gemener, och
   §8.13 säger att rubriken ska bära "samma behandling som projektvyns h1" - som är gemener med
   dämpad TLD. Versalerna var alltså både fel typografi och en av två orsaker till klippningen. */
.comptable .th-comp .sortable,
.comptable .th-comp .thlabel {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--fs-sm);
}
/* .thead .sortable har gap: 5px mellan sina inline-flex-barn, och buildCompetitorHeaderCell lägger
   .dname, .tld och pilen som SYSKON i knappen - gapet hamnade därmed mitt i domännamnet och
   rubriken lästes som "klarahem .com". Stapelvyns .thlabel hade redan gap: 0; knappvarianten fick
   det aldrig. Domännamnet är EN identifierare och ska sättas som en enhet; blanksteget före
   sorteringspilen ligger som en egen textnod i DOM:en (JS) och överlever gap: 0. */
.comptable .th-comp .sortable { gap: 0; }
/* Sorteringspilen är en etikett BREDVID identifieraren, inte en del av den - den behöver sitt eget
   luftrum tillbaka när knappens gap försvann. 5px är samma mått som .thead .sortable:s ursprungliga
   gap, så sökordstabellens och konkurrenttabellens rubriker sätts fortfarande likadant; det enda som
   ändrats är att domännamnet inte längre spräcks.
   (Ingen .you-regel här: skärmtabellens rubrik bär sedan 2026-09-09 ingen synlig "du"-markör alls,
   se buildCompetitorHeaderCell. Utskriftens .printcomptable th .you har sina egna regler.) */
.comptable .th-comp .sortable .arrow { margin-left: 5px; }

/* F88: i stapelvyn byggs en icke-egen konkurrentrubrik som .thlabel i stället för som knapp -
   kolumnen finns inte där, alltså får den inte gå att sortera på. Etiketten ärver rubrikcellens
   layout men inget av knappens klick-affordans: ingen pekare, ingen hovring, ingen fokusring.
   Att den ser oklickbar ut är hela poängen - en kontroll som inte finns ska inte se ut som en. */
.comptable .th-comp .thlabel {
  display: inline-flex;
  align-items: baseline;
  gap: 0;
  color: var(--text);
}

/* Luckans dämpade prick (F84) - formen efter .cell-pos .gapmark ovan, men FYLLD (inte ihålig): en
   ring hade lånat gapmark:ens redan etablerade betydelse ("dagens mätning saknas") för ett annat
   begrepp ("mätningen har ingen lagrad konkurrentsnapshot"). Eget aria-label i markupen (JS), inte
   bara title - en lucka får aldrig se ut som eller låta som tankstrecket. */
.gapdot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-faint);
  cursor: help;
}

/* Skimrande platshållare — exakt siffrans höjd så raden inte hoppar.
   Basen är --border, inte --bg-elev-2: annars smälter plattan in i den
   hovrade eller utfällda radens egen bakgrund och tappar sin form. */
.shimmer {
  display: inline-block;
  width: 42px;
  height: 24px;
  border-radius: var(--r-sm);
  background: linear-gradient(100deg, var(--border) 35%, var(--border-strong) 50%, var(--border) 65%);
  background-size: 220% 100%;
  animation: shimmer 1.4s linear infinite;
  vertical-align: middle;
}
@keyframes shimmer {
  from { background-position: 120% 0; }
  to   { background-position: -100% 0; }
}

.pos-land { animation: posland var(--dur-slow) var(--ease); }
@keyframes posland {
  from { opacity: 0; transform: scale(1.08); }
  to   { opacity: 1; transform: scale(1); }
}
.pos-pulse {
  position: absolute;
  inset: -3px -7px;
  border-radius: var(--r-sm);
  background: var(--accent-dim);
  animation: pospulse 700ms ease-out forwards;
  pointer-events: none;
}
@keyframes pospulse {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Förändringspill — riktningen bärs av tecken OCH färg */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  padding: 2px 8px;
  border-radius: var(--r-sm);
  line-height: 18px;
}
/* Tecknet är den andra informationsbäraren vid färgblindhet och måste
   kunna läsas tvärs över ett mötesbord — 8 px räckte inte. */
.pill .tri { font-size: 10px; font-weight: var(--fw-bold); line-height: 1; transform: translateY(-0.5px); }
.pill-up   { color: var(--up);   background: var(--up-dim); }
.pill-down { color: var(--down); background: var(--down-dim); }
/* "Vi mätte, inget hände" visas som 0 i muted; "vi har inget att jämföra
   med" som streck i faint. Tidigare såg de fem tillstånden likadana ut. */
.pill-flat { color: var(--flat); background: none; padding-left: 2px; cursor: help; }
.pill-none { color: var(--text-faint); background: none; padding-left: 2px; cursor: help; }
/* En ändpunkt utanför topp 50: riktningen är känd, magnituden är det inte.
   Pillen bär därför bara tecknet — F76a förbjuder att räkna ut en siffra när
   någon av ändpunkterna ligger utanför topp 50, den hade varit påhittad. */
.pill-edge { padding: 2px 7px; cursor: help; }
.pill-edge .tri { font-size: 12px; }

.cell-fade { animation: fadein var(--dur-slow) var(--ease); }
@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }

/* Sökvolymen (M5-4c, §0/F78): heltal, tabular-nums, högerställd som positionscellen - sorteras
   "störst först" (§8.12). Tankstrecket för en saknad volym återanvänder .failed (samma dämpade
   ton och cursor: help som positions-/url-cellernas motsvarande tankstreck). */
.cell-vol {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}
.cell-vol .failed { color: var(--text-faint); cursor: help; }

/* Rankande URL — mono, domänen dämpad så ögat landar på sidan. Explicit line-height (inte
   ärvd 1.5): .cell-url.none nedan byter till ett större typsnitt/grad, och ett ärvt UNITLESS
   line-height räknas om per elementets EGET font-size - 12 px mono och 13 px sans gav då två
   olika radhöjder och raden hoppade 1 px mellan en riktig URL och tankstrecksvarianten (§8.4). */
.cell-url {
  font-family: var(--mono);
  font-size: 12px;
  line-height: 18px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Värddelen skrivs bara ut när den avviker från projektets egen domän.
   Annars äter den bredden och trunkerar bort sökvägen, som är det ögat
   ska landa på (spec §8.8). Fullständig URL ligger i title. */
.cell-url .host { color: var(--text-faint); }
.cell-url .path { color: var(--text-muted); }
.cell-url.ownhost .path { color: var(--text); }
.cell-url.none {
  font-family: var(--font);
  font-size: var(--fs-sm);
  color: var(--text-faint);
  cursor: help;
}

.kw.measuring .cell-url { color: var(--text-faint); }

/* M5-4c (§0/F72): mät om-knappen, en åttonde... nej, sjätte kolumn - egen cell, skild från
   inaktiveringens .cell-act. .btn-sm/.btn-ghost är A:s befintliga knappformer (§8.2); det
   spärrade läget är ett riktigt disabled-attribut (avvägning 7), som ärver samma färgtrappa som
   .btn-primary:disabled (§8.2) i stället för att duplicera den. */
.th-measure { text-align: right; }
.cell-measure { display: flex; justify-content: flex-end; }
/* .btn/.btn-sm/.btn-ghost ger form och höjd (§8.2) - bara det spärrade läget är eget, och det
   speglar .btn-primary:disabled ordagrant i stället för att uppfinna en ny färgtrappa. */
.measurebtn:disabled {
  background: var(--bg-elev-2);
  border-color: var(--border);
  color: var(--text-faint);
  cursor: default;
}

/* M5-4b, avvägning 1: den sjunde cellen, husets enda radåtgärd (F30). Proportionerna och
   färgtrappan speglar .kebab (§9.5) - men utlöser direkt i stället för att öppna en meny, eftersom
   det bara finns en åtgärd. */
.th-act { text-align: right; }
.cell-act { display: flex; justify-content: flex-end; }
.rowact {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: var(--r-sm);
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-faint);
  cursor: pointer;
  /* Dämpad till osynlig tills raden hovras eller knappen får fokus (avvägning 13) - 500 alltid
     synliga glyfer bryter mot Designgrundens lugna yta. Ett osynligt men fokuserbart element är
     annars en tillgänglighetsfälla - :focus-visible/:focus-within nedan, plus @media (hover: none),
     garanterar att den ändå går att se. opacity ingår MEDVETET inte i transition: ett tangentbords-
     tabb till knappen ska göra den synlig i samma ögonblick den får fokus, inte efter en
     övertoningsfördröjning - bara färg/kant/bakgrund (det diskreta hover-svaret) toningsanimeras. */
  opacity: 0;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.row:hover .rowact,
.row:focus-within .rowact,
.rowact:focus-visible {
  opacity: 1;
}
.rowact:hover { color: var(--text); border-color: var(--border); background: var(--bg-elev-2); }
.rowact:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--accent); }
/* En spärrad knapp (begäran i luften efter ett klick, avvägning 3) ska stå kvar synlig - annars
   tappar :hover/:focus-within sitt grepp när elementet blir disabled (webbläsare slutar route:a
   pekar-/fokushändelser dit) och knappen skulle osynliggöras mitt i sin egen återkoppling. */
.rowact:disabled { cursor: default; opacity: 1; }
/* Ikonen: två lodräta stänger, ren CSS (inte SVG - se buildDeactivateIcon i seoanalys.js för
   varför). Aldrig röd/accent (§6.1) - ärver knappens color, som redan går text-faint → text. */
.rowicon { display: inline-flex; gap: 3px; width: 8px; height: 12px; }
.rowicon .bar { flex: 1; background: currentColor; border-radius: 1px; }
/* Pekskärm saknar hover helt - en knapp som bara syns vid hover vore i praktiken permanent osynlig
   där, inte bara diskret. */
@media (hover: none) {
  .rowact { opacity: 1; }
}

.kw.enter .row { animation: rowin 260ms var(--ease) both; }
@keyframes rowin {
  from { opacity: 0; transform: translateY(-7px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ============================================================
   Utfälld trendgraf
   ============================================================ */

.detail {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-slow) var(--ease);
}
.kw.open .detail { grid-template-rows: 1fr; }
.detail-inner {
  overflow: hidden;
  min-height: 0;
  background: var(--bg-elev-2);
}
.detail-pad {
  padding: var(--s3) var(--s5) var(--s5);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease);
}
.kw.open .detail-pad { opacity: 1; }

.detail-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  padding-bottom: var(--s3);
}
.detail-head .glabel {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}
.detail-head .stats {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.detail-head .stats strong { color: var(--text); font-weight: var(--fw-medium); }
.detail-head .stats .sep { color: var(--text-faint); margin: 0 6px; }

.graph-wrap { position: relative; }
.graph-wrap svg { display: block; width: 100%; }
/* Hjälplinjer i --border gav 1,14:1 och försvann helt vid skärmdelning.
   --text-faint vid 0,7 ger ~3,1:1 mot den utfällda radens --bg-elev-2. */
.graph-grid line  { stroke: var(--text-faint); stroke-opacity: var(--op-ink-faint); }
.graph-cap line   { stroke: var(--text-faint); stroke-dasharray: 3 4; stroke-opacity: var(--op-ink-faint); }
.graph-miss line  { stroke: var(--text-muted); stroke-dasharray: 2 4; stroke-opacity: var(--op-ink-faint); }
.graph-line       { stroke: var(--accent-ink); stroke-width: 2; fill: none; }
.graph-dot        { fill: var(--accent-ink); }
.graph-dot-capped { fill: var(--bg-elev-2); stroke: var(--text-faint); }
.graph-label      { font-family: var(--mono); font-size: 10px; fill: var(--text-muted); }
/* Markör vid den punkt tooltipen gäller — utan den går det inte att se
   vilken av flera intilliggande punkter talet avser. */
.graph-cursor line   { stroke: var(--text-faint); stroke-opacity: var(--op-ink-faint); stroke-width: 1; }
.graph-cursor circle { fill: none; stroke: var(--accent-ink); stroke-width: 2; }
.graph-note {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin-top: 10px;
}
/* När serien består av en enda punkt, eller aldrig legat inom topp 50, är
   texten huvudsaken och grafen noteringen — inte tvärtom. */
.graph-note.lead {
  margin: 2px 0 12px;
  font-size: var(--fs-base);
  color: var(--text);
  max-width: 64ch;
}
.graph-status {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  padding: 10px 0 4px;
}

.graph-tip {
  position: absolute;
  pointer-events: none;
  background: var(--bg-tip);
  box-shadow: var(--shadow-panel);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  padding: 7px 10px;
  line-height: 1.45;
  white-space: nowrap;
  z-index: 10;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.graph-tip.visible { opacity: 1; }
.graph-tip .d { color: var(--text-muted); font-size: var(--fs-xs); }
.graph-tip .p { font-weight: var(--fw-bold); font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.graph-tip .u {
  color: var(--text-faint);
  font-family: var(--mono);
  font-size: 10.5px;
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ============================================================
   Tomtillstånd — egen komponent, aldrig en tom tabell
   ============================================================ */

.empty {
  padding: var(--s8) var(--s5);
  text-align: center;
}
.empty h2 {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  letter-spacing: -0.01em;
}
.empty p {
  margin-top: var(--s2);
  color: var(--text-muted);
}
.empty .btn { margin-top: var(--s5); }

/* ============================================================
   Projektlista
   ============================================================ */

.projlist {
  margin-top: var(--s5);
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}
.prow {
  display: grid;
  grid-template-columns: minmax(200px, 1.4fr) 180px 110px 150px 130px 20px;
  gap: var(--s4);
  align-items: center;
  min-height: 56px;
  padding: 8px var(--s5);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
/* Kolumnrubriker över projektlistan. Samma rutnät som .prow så att varje
   etikett står exakt över sin kolumn. */
.plisthead {
  display: grid;
  grid-template-columns: minmax(200px, 1.4fr) 180px 110px 150px 130px 20px;
  gap: var(--s4);
  align-items: end;
  padding: 0 var(--s5) 8px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}
/* Skalan under rubriken talar om vad staplarnas x-led betyder: bäst position
   till vänster, allt utanför topp 50 längst till höger. */
.plisthead .maplbl { display: flex; flex-direction: column; gap: 3px; }
.plisthead .scale {
  display: flex;
  justify-content: space-between;
  width: 180px;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.plisthead .scale span:last-child { color: var(--text-faint); }

.prowitem + .prowitem .prow { border-top: 1px solid var(--border); }
.prow:hover { background: var(--bg-elev-2); }
.prow:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--accent); }
.prowitem:first-child .prow { border-radius: var(--r-lg) var(--r-lg) 0 0; }
.prowitem:last-child .prow  { border-radius: 0 0 var(--r-lg) var(--r-lg); }

.prow .domain {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  letter-spacing: -0.005em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.prow .kwcount {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
/* Listans rörelse räknar SÖKORD, tabellens pill räknar PLACERINGAR. Samma
   pillform för båda gjorde "▲8" tvetydig utan någon synlig enhet — här är det
   en räkning i text, och pillformen förblir reserverad för positionsändring. */
.prow .moves {
  display: flex;
  align-items: baseline;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Signalfärgen sitter på glyfen, inte på hela strängen (spec §9.2). Med
   "▲4 upp" helt i grönt bar projektlistan produktens starkaste färg i 13 px
   över sex rader, medan samma information i tabellen är 12 px på en tonad
   platta — dubbelkodningen bärs av glyf + ord, inte av en färgyta. */
.prow .moves .mv { display: inline-flex; align-items: baseline; gap: 3px; color: var(--text-muted); }
.prow .moves .mv.up .tri   { color: var(--up); }
.prow .moves .mv.down .tri { color: var(--down); }
.prow .moves .tri { font-size: 9px; font-weight: var(--fw-bold); }
.prow .moves .sep { color: var(--text-faint); margin: 0 6px; }
.prow .moves .moveflat { color: var(--text-muted); cursor: help; }
.prow .moves .movenone { color: var(--text-faint); cursor: help; }

/* Tomt projekt: notisen spänner över karta-, antals- och rörelsekolumnerna och
   börjar där minikartan börjar — en vänsterställd text i den högerställda
   antalskolumnen fick raden att se trasig ut i stället för tom. */
.prow .emptycells {
  grid-column: 2 / -2;
  font-size: var(--fs-sm);
  color: var(--text-faint);
}
.prow .measure {
  display: inline-flex;
  align-items: center;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.prow .chev { color: var(--text-faint); font-size: var(--fs-md); text-align: right; }
.prow .emptynote { font-size: var(--fs-sm); color: var(--text-faint); }

/* ============================================================
   Adminvy
   ============================================================ */

/* ============================================================
   Prissättningskortet (M7-8, spec §9.5.1) — adminvyns kurs och enhetspriser
   ============================================================ */

.pricingcard { margin-top: var(--s5); }

/* Mikrorubrik i samma stil som kolumnrubrikerna och kartans etikett — kortet
   ska läsa som en del av tabellsystemet, inte som en egen komponentfamilj. */
.pricingcard .cardhead {
  padding: var(--s4) var(--s5) 0;
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}

.pricingcard { --pcols: 120px 140px minmax(200px, 1fr) 120px 120px 120px; }

.pricingcard .pricerow {
  display: grid;
  grid-template-columns: var(--pcols);
  gap: var(--s4);
  align-items: center;
  min-height: 48px;
  padding: 0 var(--s5);
}

.pricingcard .phead > * {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  white-space: nowrap;
  min-width: 0;
  overflow: clip;
  overflow-clip-margin: 4px;
}
.pricingcard .pricerow + .pricerow { border-top: 1px solid var(--border); }

.pricingcard .pmonth { font-size: var(--fs-base); font-weight: var(--fw-medium); }
/* Kursen och enhetspriserna är belopp, inte koder: Inter med tabular-nums,
   aldrig --mono. Hårt mellanslag (U+00A0, skrivet av PricingNumberText) håller
   ihop "9,49 kr/USD". */
.pricingcard .pnum {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.pricingcard .psource {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  white-space: nowrap;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pricingcard .pact { justify-self: end; }

/* Låst månad: gråtonen bär inte låsningen ensam — hänglåset och ordet
   "Avslutad" gör det (§9.5). Ingen inaktiverad knapp: raden är låst för
   alltid, inte just nu. */
.pricingcard .locked .pmonth,
.pricingcard .locked .pnum,
.pricingcard .locked .psource { color: var(--text-muted); }
.pricingcard .plock {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--fs-xs);
  color: var(--text-faint);
  white-space: nowrap;
}
.pricingcard .plock svg { flex: none; }

.pricingcard .pdash { color: var(--text-faint); }

/* Den staplade formen har inget att göra över 990 px */
.pricingcard .pstack { display: none; }

@media (max-width: 990px) {
  /* Källa och de två enhetspriserna får inte plats bredvid varandra. Kortet
     byter därför till staplat läge i stället för att korta rubriker: ett
     avkortat "KÄLLA" hade ändå inte gett kolumnen den bredd den behöver. */
  .pricingcard .phead {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .pricingcard .pricerow {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
    padding: var(--s4) var(--s5);
  }
  /* Värdecellerna byter till den synliga staplade formen, men får INTE
     försvinna ur tillgänglighetsträdet: .pstack är aria-hidden, så ett
     display: none här hade tagit bort kurs, källa och de två enhetspriserna
     helt för en skärmläsare — och 200 % zoom på 1920 px landar i just det här
     läget (WCAG 1.4.4). Samma visuellt-dolda mönster som .phead ovan håller
     därför cellstrukturen hel mot de sex kolumnrubrikerna. */
  .pricingcard .pricerow > .pcell {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .pricingcard .pstack { display: block; grid-column: 1; min-width: 0; }
  .pricingcard .pricerow > .pmonth { grid-column: 1; grid-row: 1; min-width: 0; }
  .pricingcard .pricerow > .pact,
  .pricingcard .pricerow > .plockcell { grid-column: 2; grid-row: 1; justify-self: end; }
  .pricingcard .pstack {
    grid-row: 2;
    margin-top: 4px;
    font-size: var(--fs-sm);
    color: var(--text-muted);
  }
  /* Ett enskilt värde ("9,49 kr/USD", "Riksbanken · 1 aug") får aldrig brytas
     mitt itu, men raden får brytas mellan värdeparen när utrymmet tryter. */
  .pricingcard .pstack span:not(.sep) { display: inline-block; white-space: nowrap; }
  .pricingcard .pstack .sep { color: var(--text-faint); margin: 0 6px; }
}

.admtable {
  /* Kostnadskolumnerna bär sina egna rubriker: "SENASTE HELMÅNAD · JULI ↕"
     mäter 185 px och sköt tidigare ut över grannkolumnen. Statuskolumnen
     ("AKTIV" / "SPÄRRAD") lämnar ifrån sig bredden. */
  --acols: minmax(230px, 1.7fr) 92px 148px 188px 148px 44px;
}

.admtable .thead { grid-template-columns: var(--acols); }

/* Radhöjden är MINST 56 px enligt spec §9.5 (M7-9, städning: inte längre en fast höjd) - en rad
   utan chip eller kvotnotis fyller exakt 56 px, men "Kvot nådd"-notisen ligger numera i normalt
   flöde under stapeln (se .uquota .capnote) i stället för absolutpositionerad, så raden växer med
   sitt eget innehåll i stället för att låta notisen sticka ner i raden under. */
.urow {
  display: grid;
  grid-template-columns: var(--acols);
  gap: var(--s4);
  align-items: center;
  min-height: 56px;
  padding: 0 var(--s5);
}
.user + .user .urow { border-top: 1px solid var(--border); }
.urow:hover { background: var(--bg-elev-2); }
.user:last-child .urow { border-radius: 0 0 var(--r-lg) var(--r-lg); }
.user:last-child.open .urow { border-radius: 0; }

.ident { min-width: 0; }
.ident .name {
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  line-height: 20px;
  display: flex;
  align-items: center;
  gap: var(--s2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ident .name .chip {
  line-height: 14px;
  padding: 1px 7px;
  flex: none;
}
.ident .mail {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.user.blocked .ident .name,
.user.blocked .ident .mail { color: var(--text-muted); }

.ustatus { font-size: var(--fs-sm); color: var(--text-muted); }
.ustatus .invited { color: var(--text-faint); cursor: help; }
/* M7-6c, Ö12: giltighetsnotisen under "Inbjuden" - samma dämpade andrarad som .uquota .capnote,
   men UTAN absolut positionering. En inbjuden rads kvotcell är alltid tom, så de två raderna
   (statuschip + notis) ryms inom radhöjden 56 px utan att behöva lyfta ur flödet. Notisen får
   spilla några pixlar åt höger i den smala kolumnen - det är det medvetna priset. */
.ustatus .invexp {
  margin-top: 2px;
  font-size: var(--fs-xs);
  line-height: 14px;
  color: var(--text-faint);
  white-space: nowrap;
}

.uquota { position: relative; }
.uquota .bar {
  display: block;
  margin-top: 6px;
  width: 64px;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
  overflow: hidden;
}
.uquota .bar i { display: block; height: 100%; border-radius: 2px; background: var(--text-faint); }
.uquota.near .bar i { background: var(--text-muted); }
.uquota.full .bar i { background: var(--text); }
/* I normalt flöde UNDER stapeln (M7-9, städning) - tidigare absolutpositionerad för att
   aldrig göra raden högre än 56 px, men det lät notisen sticka ner i raden under i stället
   (.urow har ingen fast höjd längre, se dess kommentar). Till höger om stapeln hängde
   etiketten fritt mellan två kolumner och såg bortglömd ut (spec §9.5), så den ligger kvar
   UNDER, bara inte utanför flödet. */
.uquota .capnote {
  margin-top: 3px;
  font-size: var(--fs-xs);
  line-height: 14px;
  color: var(--text-muted);
  white-space: nowrap;
}
.uquota .none { font-size: var(--fs-sm); color: var(--text-muted); }

.ucost {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-base);
}
.ucost.na { color: var(--text-faint); cursor: help; }

.uactions { text-align: right; }
.kebab {
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: var(--r-sm);
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.kebab:hover { color: var(--text); border-color: var(--border); background: var(--bg-elev-2); }
.kebab[aria-expanded="true"] { color: var(--text); border-color: var(--border-strong); background: var(--bg-elev-2); }

/* Menyn är ett svävande lager och behöver egen yta plus skugga — med samma
   färg som kortet under lästes den som text ovanpå tabellraderna. */
.menu {
  position: absolute;
  z-index: 50;
  min-width: 216px;
  max-width: 316px;   /* rymmer "Ta bort administratörsrollen" + orsak på en rad */
  background: var(--bg-float);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  padding: var(--s1);
  box-shadow: var(--shadow-float);
}
.menu button {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: var(--r-sm);
  padding: 8px 10px;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.menu button:hover { background: var(--bg-float-hover); color: var(--text); }
/* Låst läge bärs inte av grå ton allena — skillnaden mot aktiva poster var
   ~1,3:1 och menyn såg ut som sex likvärdiga val. Hänglåset och den korta
   orsaken är den andra och tredje bäraren. */
/* [aria-disabled], inte :disabled (M7-9, rättat): posten är fortsatt native fokuserbar - se
   buildMenuItem i seoanalys.js för varför en riktig disabled-knapp hoppas över av tangentbordet. */
.menu button[aria-disabled="true"] {
  color: var(--text-faint);
  cursor: default;
  background: none;
  display: flex;
  align-items: center;
  gap: 7px;
}
.menu button[aria-disabled="true"]:hover { background: none; color: var(--text-faint); }
.menu button .lock { display: inline-flex; flex: none; color: var(--text-faint); }
.menu button .why {
  margin-left: auto;
  padding-left: var(--s2);
  font-size: var(--fs-xs);
  color: var(--text-faint);
  white-space: nowrap;
}
.menu hr { border: none; border-top: 1px solid var(--border-strong); margin: var(--s1) 0; }
.menu .note {
  padding: 4px 10px 6px;
  font-size: var(--fs-xs);
  line-height: 1.4;
  color: var(--text-muted);
}

/* Utfällt projektblock under en användarrad */
.uprojects {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-slow) var(--ease);
}
.user.open .uprojects { grid-template-rows: 1fr; }
.uprojects-inner { overflow: hidden; min-height: 0; background: var(--bg-elev-2); }
/* Blocket ligger 24 px indraget med en kant som visuell klammer. Med samma
   vänsterkant som föräldrarraderna — och --bg-elev-2 mot --bg-elev på 1,09:1 —
   lästes användarens projekt som sex nya toppnivåanvändare. */
.uprojects-pad {
  padding: var(--s3) var(--s5) var(--s4);
  margin-left: var(--s5);
  border-left: 2px solid var(--border-strong);
}
.uprojects h3 {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: var(--s2);
}
/* Samma kolumnraster som adminraderna ovanför. Bara den flexibla första
   kolumnen krymper av indraget, så antal och "Överlåt…" landar på exakt samma
   x som tabellens egna kolumner i stället för att rimma med ingenting. */
.uproj {
  display: grid;
  grid-template-columns: var(--acols);
  gap: var(--s4);
  align-items: center;
  padding: 7px 0;
  font-size: var(--fs-sm);
}
.uproj + .uproj { border-top: 1px solid var(--border); }
.uproj .d { grid-column: 1; font-size: var(--fs-base); font-weight: var(--fw-medium); }
.uproj .n { grid-column: 3; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.uproj .a { grid-column: 5 / span 2; text-align: right; }
.uproj .arch { color: var(--text-faint); font-size: var(--fs-sm); }
.uprojects .noproj { font-size: var(--fs-sm); color: var(--text-faint); }

/* Åtkomstspärr — visas om en icke-administratör når adminvyn (F56, F71).
   Använder tomtillståndets komponent (.empty) så att portalen inte har två
   typsystem för samma sorts yta; kortet är det enda som är eget. */
.denied {
  margin-top: var(--s5);
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

/* Kvittensrad efter en genomförd åtgärd i adminvyn */
.adminflash {
  margin-top: var(--s4);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.adminflash:empty { display: none; }

/* ============================================================
   Dialoger
   ============================================================ */

dialog {
  margin: auto;
  background: var(--bg-elev);
  box-shadow: var(--shadow-panel);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-lg);
  padding: var(--s5);
  width: min(440px, calc(100vw - 48px));
}
dialog::backdrop { background: var(--backdrop); }
dialog h2 {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  letter-spacing: -0.015em;
}
dialog .hint {
  margin-top: var(--s2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
dialog label {
  display: block;
  margin-top: var(--s4);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: var(--s2);
}
dialog input[type="text"],
dialog input[type="email"],
dialog input[type="number"],
dialog select {
  display: block;
  width: 100%;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  height: 44px;
  padding: 0 14px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
/* Selecterna hade webbläsarens egen chevron och OS-lista — det enda ostylade
   systemwidgetet i ett genomdesignat mörkt gränssnitt, med risk för en ljus
   popup vid skärmdelning. Chevronen är inbäddad SVG, ingen extern fil. */
dialog select {
  appearance: none;
  padding-right: 40px;
  background-image: var(--ic-chevron);
  background-repeat: no-repeat;
  background-position: right 14px center;
  cursor: pointer;
}
dialog select option { background: var(--bg-elev-2); color: var(--text); }
/* Talfältets native spinner är samma sorts ostylade systemwidget som selecten
   var — och dess pilar är ▲/▼, glyfer som betyder positionsrörelse i den här
   produkten (spec §3.14). Stegningen sker med piltangenter som förut. */
dialog input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
dialog input[type="number"]::-webkit-outer-spin-button,
dialog input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
dialog input::placeholder { color: var(--text-faint); }
/* M7-6c: den kopierbara inbjudningslänken - monospace gör tokenet lättare att läsa och markera
   som en enhet, samma motivering som .mono används på andra maskinlästa strängar i produkten. */
#invitelink { font-family: var(--mono); font-size: 12px; }
dialog input:focus, dialog select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
}
dialog .normalized {
  margin-top: var(--s2);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-faint);
  min-height: 18px;
}
dialog .normalized strong { color: var(--text-muted); font-weight: var(--fw-bold); }
/* Ett hinder ska läsas, inte anas. Full textstyrka och en neutral markör —
   ingen varningsfärg, eftersom rött är reserverat för nedåtgående rörelse.
   DELAD med konkurrentkortets #compnormalized (M9-5): båda ytorna är samma sak - en
   domänförhandsvisning som kan bära ett hinder - och hade var sin kopia tills den ena hann bli röd
   medan den andra var neutral. En delad regel kan inte glida isär; två kopior gjorde det. */
dialog .normalized.problem,
#compnormalized.problem {
  color: var(--text);
  display: flex;
  align-items: flex-start;
  gap: 7px;
}
dialog .normalized.problem::before,
#compnormalized.problem::before {
  content: "!";
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 1px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--bg-elev-2);
  border: 1px solid var(--border-strong);
  color: var(--text-muted);
  font-size: 11px;
  font-weight: var(--fw-bold);
  line-height: 1;
}
/* Blockerande fel — neutral platta, aldrig röd (spec §6.1) */
dialog .blockmsg {
  margin-top: var(--s4);
  background: var(--bg-elev-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  padding: var(--s3) var(--s4);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
/* M7-8: källraden under kursfältet i prissättningsdialogen - vad som skrivs över. */
dialog .fieldnote {
  margin-top: 5px;
  font-size: var(--fs-xs);
  color: var(--text-faint);
}
dialog .actions {
  margin-top: var(--s5);
  display: flex;
  justify-content: flex-end;
  gap: var(--s3);
  flex-wrap: wrap;
}
/* Två likvärdiga vägar staplas i full bredd; "Avbryt" är en utväg och ligger
   som diskret textknapp under dem — inte som ett tredje likvärdigt val. */
dialog .actions.stack { flex-direction: column; align-items: stretch; gap: var(--s2); }
dialog .actions.stack .btn { width: 100%; }
dialog .actions.stack .btn-quiet { margin-top: var(--s1); }

/* ============================================================
   Inloggning
   ============================================================ */

.loginpage { min-height: 100vh; display: grid; place-items: center; padding: var(--s6) var(--s5); }
.login { width: 100%; max-width: 380px; }
.login .wordmark { font-size: var(--fs-lg); gap: 12px; }
.login .wordmark .mark { width: 32px; height: 32px; border-radius: 10px; }

.login-map { margin-top: var(--s7); }
.login-map svg { display: block; width: 100%; height: auto; }

.login h1 {
  margin-top: var(--s6);
  font-size: 28px;
  font-weight: var(--fw-bold);
  letter-spacing: -0.02em;
  line-height: 1.15;
}
.login-sub {
  margin-top: var(--s3);
  font-size: var(--fs-base);
  color: var(--text-muted);
}
/* M2-6, §9.1: feltillståndet (?fel=...) renderas i --down på ren --bg - inget kort, ingen
   platta - ovanför knapparna, och ersätter inget annat i layouten. */
.login-error {
  margin-top: var(--s5);
  font-size: var(--fs-base);
  color: var(--down);
}
.login-btns {
  margin-top: var(--s6);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}
.idbtn {
  display: flex;
  align-items: center;
  gap: var(--s3);
  height: 44px;
  padding: 0 var(--s4);
  background: var(--bg-elev-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  color: var(--text);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.idbtn:hover { background: var(--bg-elev); }
.idbtn svg { display: block; flex: none; }
/* Fotnoten bär ett krav (F08) och sätts därför i --text-muted i stället för --text-faint som
   DESIGNSPEC §9.1 punkt 5 föreskriver - båda tokens klarar AA, så avvikelsen är ett medvetet val
   för extra marginal, inte ett fel. */
.login-foot {
  margin-top: var(--s5);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* ============================================================
   Utskriftsvyn (M9-8a/M9-8b, DESIGNSPEC §9.6)
   En helt ny yta utan motsvarighet i prototypen - §9.6 lades till 2026-09-01, efter att
   prototypen frystes (samma situation som konkurrentkortet, M9-4). Selektorerna nedan bär
   därför ingen "avsteg från prototypen"-kommentar; det finns ingen prototypregel att avvika
   från. Återanvänder befintliga tokens och redan kopierade regler (.page, .btn/.btn-ghost,
   .tld, .pill*, .cell-pos .measured-on, .visually-hidden, .gapdot) i stället för att skriva om
   dem. M9-8b lägger till de fyra växlarna (.printtoggle) i samma .printbar-rad, konkurrent-
   tabellen (.printcomp/.printcomptable, §9.6 punkt 5) och @media print-blocket längst ned.
   ============================================================ */

.printbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s3);
  padding-bottom: var(--s4);
}

/* Kryssrutan och dess svenska etikett i EN <label> (avvägning 2: native checkbox, ingen
   aria-pressed-knapp) - ingen egen bock-grafik, webbläsarens/skärmläsarens egen kryssruta bär
   redan tillstånd och tangentbord gratis. */
.printtoggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  cursor: pointer;
}

.printhead {
  margin-bottom: var(--s2);
}

/* overflow-wrap är samma avsteg som .view-head h1 (rad 525-528) och samma orsak -
   ett domännamn utan bindestreck sprängde annars sidbredden vid 380 px. */
.printhead h1 {
  font-size: 28px;
  font-weight: var(--fw-bold);
  margin: 0;
  overflow-wrap: anywhere;
}

.printdate {
  margin: var(--s2) 0 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.printsum {
  margin: var(--s4) 0;
  color: var(--text-muted);
}

.printempty {
  margin: var(--s4) 0;
  color: var(--text-muted);
}

/* Tabellen rullar i sitt EGET spår när kolumnerna inte ryms i den smalaste stödda bredden
   (§7: sidan får aldrig tvinga fram sidbredd) - samma etablerade mönster som .comptable
   .gridscroll, se NarrowViewport_WithFiveCompetitors_NeverForcesPageWidth. */
.printtable-scroll {
  overflow-x: auto;
}

/* table-layout: fixed (inte auto): en auto-layoutad tabell sätter varje kolumns bredd efter dess
   längsta INNEHÅLL, inte efter th/td-reglerna - en enda lång URL eller sökordsfras sprängde då
   tabellen i stället för att trunkeras, och .printtable-scroll rullade bort resten. Fixed layout
   låter breddreglerna nedan (satta på th, som en fixed-tabell tar sina kolumnbredder ifrån) styra,
   så att .cell-url:s ellipsis (rad 1539) faktiskt får en definit bredd att klippa mot. */
.printtable {
  width: 100%;
  min-width: 480px;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: var(--fs-sm);
}

/* Sökvolym- och positionskolumnerna är breddade så att de rymmer sitt innehåll vid tabellens
   smalaste stödda bredd (min-width 480px) - sökvolymens tusentalsavgränsare är ett hårt
   mellanslag, så talet räknas som ETT obrytbart ord, och positionskolumnen bär både siffran och
   mätdatumraden (se .cell-pos-regeln nedan). Sökord och URL krymper motsvarande men behåller sin
   dubbelt så stora andel av resten.
   M9-8b flyttar breddreglerna från :nth-child till [data-print-col] (taskplanens avvägning 4):
   klienten tar bort en avstängd kolumn ur DOM:en, vilket förskjuter varje nth-child ett steg -
   sökvolymens 16 % skulle då landa på positionskolumnen. Procentsatserna är oförändrade; summan
   understiger medvetet 100 % när en kolumn är av, och table-layout: fixed fördelar överskottet
   proportionellt utan en regel per kombination. */
.printtable th[data-print-col="phrase"] { width: 25%; }
.printtable th[data-print-col="volume"] { width: 16%; }
.printtable th[data-print-col="position"] { width: 17%; }
.printtable th[data-print-col="change"] { width: 14%; }
.printtable th[data-print-col="url"] { width: 28%; }

/* Skyddsnät mot nästa gång ett tal blir bredare än kolumnen (större sökvolymer, ett längre
   procenttal) - hellre en synlig "…" än den tysta bortklippningen ovan beskriver. Förändringskolumnen
   bär samma skydd: dess innehåll är triangel + tal ("▲ 36"), alltså bredare än talet ensamt, och
   den klippte tyst vid 480px innan skyddet fanns här. Kolumnbredderna är avsiktligt orörda - att
   bredda positionskolumnen för sifferns typografi är en egen avvägning (uppskjuten 2026-09-06). */
.printtable td[data-print-col="volume"],
.printtable td[data-print-col="change"] {
  white-space: nowrap;
  text-overflow: ellipsis;
}

.printtable th,
.printtable td {
  text-align: left;
  padding: var(--s3) var(--s3);
  border-bottom: 1px solid var(--border);
  overflow: hidden;
}

/* Sökordsfrasen (upp till KeywordLimits.MaxPhraseLength) har ingen ellipsis - den ska gå att läsa
   hel på papperet - så den bryts inom sin fasta kolumnbredd i stället för att sprängningen av en
   auto-layoutad tabell (se kommentaren ovan) återuppstår som ett enda långt ord utan mellanslag. */
.printtable td:first-child {
  overflow-wrap: anywhere;
}

.printtable th {
  font-size: 11px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-faint);
}

.printtable .cell-url .host {
  color: var(--text-faint);
  margin-right: 2px;
}

/* Utskriftens positionskolumn (82px vid min-width 480px minus padding) är för smal för skärmens
   .cell-pos - display:flex, align-items:flex-end och 24px fet stil är byggd för en bred,
   obegränsad kolumn, och högerställt flexinnehåll klipper "mäter…" från vänster mot cellens
   vänsterkant i den smala bredden. Utskriften får sin egen, vanliga tabelltypografi i stället -
   vänsterställd, normal grad, med ellipsis som sista skyddsnät. display:table-cell (inte block)
   håller cellen kvar i radens tabellayout, så att .printtable td:s border-bottom fortsatt ritas
   mot radens höjd och inte mot innehållets - ett table-cell-barn som får display:block lyfts ur
   raden och lindas i en anonym cell av webbläsaren, vilket trappstegar radlinjen. */
.printtable .cell-pos {
  display: table-cell;
  vertical-align: middle;
  text-align: left;
  font-size: var(--fs-sm);
  font-weight: var(--fw-normal);
  font-variant-numeric: normal;
  letter-spacing: normal;
  line-height: normal;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* M9-8a-fixet (Failed med historik, §8.4): ringen och talet är EN visuell enhet, samma orsak som
   .cell-pos .poswrap:s nowrap-kommentar ovan - utan en gemensam inline-block-wrapper staplar
   .cell-pos:s flex-direction: column dem på var sin rad i stället för sida vid sida. */
.printtable .cell-pos .gapline {
  display: inline-block;
  white-space: nowrap;
}

/* Utskriftens konkurrenttabell (M9-8b, §9.6 punkt 5/§8.12) - den fjärde växelns block. Egen
   rubrik (h2, inte h1: sidan får bara ha en) och en egen, enklare tabellstomme än .printtable -
   ingen sorterbar rubrikrad, inget rullande spår per konkurrent (skärmens subgrid), bara en
   vanlig <table> som .printtable redan är. */
.printcomp {
  margin-top: var(--s5);
}

.printcomp h2 {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-faint);
  margin: 0 0 var(--s3);
}

/* table-layout: fixed, samma skäl som .printtable ovan: utan den sätter en auto-layoutad tabell
   varje kolumns bredd efter sitt längsta INNEHÅLL - ett långt domännamn eller en lång sökordsfras
   sprängde då tabellen bredare än .printtable-scroll:s synliga spår, som klipper i stället för att
   rulla i pappersmedia. Bara sökordskolumnen (:first-child) får en egen procentandel; de
   konkurrentkolumner tabellen faktiskt har (1-4, F81-taket) delar automatiskt resten jämnt mellan
   sig under fixed layout, utan en regel per antal. */
.printcomptable {
  width: 100%;
  min-width: 480px;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: var(--fs-sm);
}

.printcomptable th:first-child,
.printcomptable td:first-child {
  width: 26%;
}

.printcomptable th,
.printcomptable td {
  text-align: left;
  padding: var(--s3) var(--s3);
  border-bottom: 1px solid var(--border);
  overflow: hidden;
}

/* Sökordsfrasen bryts inom sin fasta kolumnbredd i stället för att sprängningen en auto-layoutad
   tabell hade gett återuppstår som ett enda långt ord (samma regel som .printtable td:first-child). */
.printcomptable td:first-child {
  overflow-wrap: anywhere;
}

/* Rubrikcellen själv bryts eller klipps ALDRIG som helhet - nowrap+ellipsis satt direkt på .th
   klippte tidigare hela innehållet (namn+TLD+"du") som EN enhet från höger, vilket vid A4:s
   innehållsbredd med tre konkurrenter tog bort egen-kolumnens dämpade "du"-markör (sist i cellen)
   och gav två domäner med gemensamt prefix identiska synliga rubriker. .thlabel nedan (samma
   flex-baserade mönster som skärmens .thead .sortable) tar över krympningen; .th självt sätter
   bara typografin. */
.printcomptable th {
  font-size: 11px;
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-faint);
}

/* .thlabel är en vanlig inline-behållare, inte flex: ett flerradigt flexitem baselinejusteras mot
   sin FÖRSTA rad, vilket skulle placera .tld/.you bredvid domännamnets första rad i stället för
   dess sista så fort .dname radbryter. Inline-flöde följer i stället alltid textens egen sista
   rad, och begränsas naturligt av .th:s bredd (ingen max-width behövs på en inline-box - den
   egenskapen gäller bara block- och inline-block-lådor). */
.printcomptable th .thlabel {
  display: inline;
}

/* Domänrubrikerna bär INTE versalerna ovan - ett domännamn är en identifierare, samma regel som
   skärmens .th-comp .dname (rad ~1436). .dname är domännamnets EGET omslag (Utskrift.cshtml) - utan
   det ärver den nakna textnoden .printcomptable th:s text-transform: uppercase rakt av, oavsett vad
   den här undantagsregeln säger.
   Vanligt inline-flöde, inte en flex- eller box-behållare: en flerradig text bryts då normalt
   inom sin egen box och TLD:n (.tld nedan) fortsätter automatiskt på textens SISTA rad - ett
   domäns namn och dess TLD hör alltid ihop, oavsett hur många rader namnet tar. overflow-wrap:
   anywhere bryter även ett långt, obrutet domännamn (t.ex. "gemensamtlangtprefix-ett"/"-tva"/
   "-tre") i stället för att låta det svälla cellen; title på .thlabel bär hela domänen som stöd. */
.printcomptable th .dname {
  overflow-wrap: anywhere;
  text-align: left;
  text-transform: none;
  color: var(--text);
}

.printcomptable th .tld,
.printcomptable th .you {
  white-space: nowrap;
  text-transform: none;
  color: var(--text-faint);
}

/* Markupen (Utskrift.cshtml) lägger .tld och .you direkt intill varandra utan mellanslag, för att
   namn och TLD ska sitta ihop utan egen separation. margin-left återskapar avståndet på .you
   specifikt, så att rubriken läses "namn.tld du", inte "namn.tlddu". */
.printcomptable th .you {
  margin-left: 4px;
}

.printcomptable td:not(:first-child) {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Ledarmarkeringen (§8.12, samma BEGREPP som skärmens via CompetitorLeaderMark, men medvetet olika
   FORM per medium): på papperet bärs den av ▲-glyfen nedan, eftersom bakgrund och box-shadow är
   bakgrundsgrafik och inte skrivs ut med webbläsarens standardinställning (§9.6). Av exakt samma
   skäl kan skärmens bläckpill (.cell-comp .lead, sedan 2026-09-08) inte användas här - en solid
   fyllning som inte skrivs ut lämnar vit text på vitt papper. Ton/ring nedan är kvar som ett extra
   spår i skärmens förhandsvisning av utskriften, inte som den bärande markören - meningen sitter i
   cellens aria-label (Utskrift.cshtml). Skillnaden är alltså inte en glömd synk. */
.printcomptable td.leader {
  background: var(--bg-elev-2);
  box-shadow: inset 0 0 0 1px var(--text-faint);
  border-radius: var(--r-sm);
}

.printcomptable td.leader .tri {
  margin-right: 3px;
  font-size: 10px;
  font-weight: var(--fw-bold);
}

/* ============================================================
   Rörelse av
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* Stillastående platta måste ändå skilja sig från både radens vanliga yta
     och dess hover-yta — annars syns platshållaren knappt alls. */
  .shimmer { animation: none; background: var(--border); box-shadow: inset 0 0 0 1px var(--border-strong); }
  .kw.enter .row { animation: none; }
  .dot-pulse { animation: none; opacity: 0.7; }
  /* Pulsspåret blir en stillastående accentlinje så länge mätning pågår.
     --accent-dim gav ~1,1:1 mot --bg och gick inte att skilja från topplistens
     vanliga kant — livesignalen var i praktiken osläckt. Opak --accent-ink ger
     5,0:1, och 3 px läser som ett tillstånd i stället för som en kant. */
  body.measuring-live .topbar .pulse {
    animation: none;
    width: 100%;
    height: 3px;
    background: var(--accent-ink);
  }
  /* M5-6c: rörelsegardens *-regel ovan klipper rowpulse (@keyframes) till 0,01 ms, så en radmarkering
     som bara vore animationen vore osynlig i exakt det läget den ska synas i. Statiskt läge i
     stället (DESIGNSPEC §11 "statiskt läge"): --accent-dim ensamt ger ~1,1:1 mot radens yta - samma
     fynd som redan är löst för pulslinjen ovan - så en vänsterkant i --accent-ink (5,12:1 mot
     --bg-elev) läggs till. Formen är medvetet en annan än radens egen fokusring
     (.kw .row:focus-visible, inset heltäckande ring) så de två tillstånden inte kan förväxlas. */
  .kw.mappulse .row {
    animation: none;
    background: var(--accent-dim);
    box-shadow: inset 3px 0 0 0 var(--accent-ink);
  }
}

/* ============================================================
   Responsivt
   ============================================================ */

/* Inget flexbarn i topplisten får tvinga fram sidbredd: userchipet stack ut
   över högerkanten vid 380 px och gav hela dokumentet horisontell scroll. */
.topbar-inner > * { min-width: 0; }
.wordmark { overflow: hidden; }

@media (max-width: 990px) {
  /* M5-4c: sju spår i §0:s ordning - sökord, sökvolym, position, förändring, url, mät om, åtgärd. */
  .kwtable { --cols: minmax(150px, 1.4fr) 68px 84px 76px minmax(120px, 1fr) 72px 36px; }
  /* Konkurrentkortet rullar redan i sitt eget kort (.comptable .grid), så smalare spår här är en
     finjustering, inte en förutsättning - se NarrowViewport_WithFiveCompetitors_
     NeverForcesPageWidth, som prövar 380px direkt utan att gå via det här blocket. */
  .comptable { --cols: minmax(150px, 1.4fr) minmax(76px, max-content) repeat(var(--comp-count), minmax(76px, max-content)); }
  .admtable { --acols: minmax(180px, 1.5fr) 100px 132px 120px 120px 40px; }
  /* Kostnadsrubrikerna får inte breda ut sig över grannkolumnen */
  .th-cost .lbl-long { display: none; }
  .th-cost .lbl-short { display: inline; }
  /* "Förändring" ryms inte i th-change:s 76 px-spår under 990 px och kapades
     mot kantlinjen. */
  .th-change .lbl-long { display: none; }
  .th-change .lbl-short { display: inline; }
  /* M5-7a: "SÖKVOLYM ↕" ryms inte i th-vol:s 68 px-spår under 990 px av samma skäl. */
  .th-vol .lbl-long { display: none; }
  .th-vol .lbl-short { display: inline; }
  .page { padding: var(--s5) var(--s4) var(--s7); }
  .topbar-inner { padding: 0 var(--s4); }
  .prow { grid-template-columns: minmax(180px, 1.4fr) 110px 150px 130px 20px; }
  .prow .pmap-mini { display: none; }
  .plisthead { display: none; }
  .pmaphead .hint { display: none; }
  /* Under 900 px ryms inte domän + sökväg. Domänen är identisk på varje rad,
     så den faller bort först — sökvägen är kolumnens hela informationsvärde. */
  .kwtable .cell-url .host { display: none; }
}
@media (max-width: 815px) {
  .page { padding: var(--s4) var(--s3) var(--s7); }
  .topbar-inner { padding: 0 var(--s3); gap: var(--s4); }
  .userchip .uname { display: none; }

  /* Domännamnet får inte spilla över kanten på små skärmar */
  .view-head { gap: var(--s2); }
  .view-head h1 { font-size: 28px; }
  .view-head .chip { top: 0; }

  .addbar { flex-wrap: wrap; }
  .addcol { flex: 1 0 100%; }
  .quota { min-width: 0; flex: 1; }

  /* Kartkortets gutter följer radernas padding hela vägen ned */
  .pmapcard { padding: 10px var(--s4); }

  /* Kolumnerna faller bort i värdeordning: URL först - fullständig adress ligger kvar i
     title (§8.8), och sökvolym/mät om väger tyngre för en säljare i fält än sökvägen gör.
     Sex spår kvar: sökord, sökvolym, position, förändring, mät om, åtgärd. */
  .kwtable { --cols: minmax(120px, 1.4fr) 52px 58px 58px 64px 36px; }
  .comptable { --cols: minmax(120px, 1.4fr) minmax(64px, max-content) repeat(var(--comp-count), minmax(64px, max-content)); }
  .kwtable .th-url, .kwtable .cell-url { display: none; }
  /* "POSITION ↕" ryms inte i sin kolumn här och sköt ut över kortkanten, där
     sorteringsglyfen kapades av kantlinjen. Den korta formen säger samma sak. */
  .th-pos .lbl-long { display: none; }
  .th-pos .lbl-short { display: inline; }
  /* M5-9: th-vol:s korta form ligger redan i 990px-blocket ovan, som gäller lika mycket här
     (990 > 815) - en andra kopia här kan aldrig träda i kraft för någon bredd och är dödkod. */
  .row, .thead { padding-left: var(--s4); padding-right: var(--s4); gap: var(--s3); }
  .detail-pad { padding: var(--s3) var(--s4) var(--s4); }
  .tabletools {
    padding: var(--s3) var(--s4);
    flex-direction: column;
    align-items: stretch;
    gap: var(--s2);
  }
  .tabletools .filter input { width: 100%; }

  .prow {
    grid-template-columns: 1fr 90px 20px;
    padding-left: var(--s4);
    padding-right: var(--s4);
  }
  /* Notisen får en egen kolumnbredd på smala skärmar — i 90 px-kolumnen bröts
     "Inga sökord ännu" till två rader mitt i en annars enradig lista. */
  .prow-empty { grid-template-columns: 1fr auto 20px; }
  .prow-empty .emptycells { white-space: nowrap; }
  .prow .moves, .prow .measure, .prow .pmap-mini { display: none; }

  .admtable { --acols: minmax(110px, 1.2fr) 78px 96px 36px; }
  .th-cost, .ucost { display: none; }
  /* "SÖKORD / KVOT ↕" ryms inte i 96 px — kolumnen visar kvoten, rubriken med. */
  .th-quota .lbl-long { display: none; }
  .th-quota .lbl-short { display: inline; }
  .urow { padding-left: var(--s4); padding-right: var(--s4); gap: var(--s3); }
  .uprojects-pad { padding-left: var(--s4); padding-right: var(--s4); margin-left: var(--s4); }
  /* Fyra kolumner kvar: antalet flyttar in i statuskolumnen och knappen tar
     de två sista, så att blocket fortsätter rimma med raderna ovanför. */
  .uproj .n { grid-column: 2; }
  .uproj .a { grid-column: 3 / span 2; }
}

/* Under 480 px är sökfrasen radens identitet och ska offras sist — den bryts
   till två rader i stället för att tre rader i följd läser "badrumsrenove…". */
@media (max-width: 480px) {
  /* Sökvolym och mät om offras här (efter URL vid 815 px) - fyra spår kvar: sökord, position,
     förändring, åtgärd. En telefonbredd rymmer inte sex kolumner och ett radåtgärdsspår samtidigt. */
  .kwtable { --cols: minmax(0, 1fr) 52px 52px 36px; }
  .comptable { --cols: minmax(120px, 1fr) minmax(56px, max-content) repeat(var(--comp-count), minmax(56px, max-content)); }
  .th-vol, .cell-vol, .th-measure, .cell-measure { display: none; }
  .row, .thead { padding-left: var(--s3); padding-right: var(--s3); gap: 10px; }
  .thead { height: 34px; }
  .cell-kw {
    white-space: normal;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    line-height: 1.35;
  }
  .cell-kw .newtag { margin-left: 6px; }
  .row { padding-top: 7px; padding-bottom: 7px; }
  .detail-pad { padding: var(--s3) var(--s3) var(--s4); }
  .prow { padding-left: var(--s3); padding-right: var(--s3); }
  .topbar-inner { gap: var(--s3); }
  .topnav a { padding: 6px 8px; }
  /* Ordmärkets text viker, brickan står kvar som hemlänk: vid 380 px räckte bredden inte
     till navigering, växlare och chip samtidigt, och växlaren ritades rakt över
     "Administration". overflow: hidden är sista utvägen om innehållet ändå inte ryms. */
  .wordmark { font-size: 0; gap: 0; }
  .topnav { overflow: hidden; }
  .pmapcard { padding: 10px var(--s3); }

  /* Adminlistan tvingade fram 385 px sidbredd vid 380 px viewport: identiteten
     får krympa fritt och raderna följer tabellens smalare gutter. */
  .admtable { --acols: minmax(0, 1fr) 72px 92px 36px; }
  .urow { padding-left: var(--s3); padding-right: var(--s3); }
  .uprojects-pad { padding-left: var(--s3); padding-right: var(--s3); margin-left: var(--s3); }
}

/* ============================================================
   Utskriftens @page/@media print-block (M9-8b, F93, §9.6/§6.2)
   @page ligger UTANFÖR @media print - det är en egen at-regel som webbläsaren bara tillämpar vid
   utskrift oavsett var i filen den står; att nästla den inuti @media print vore bara brus.
   ============================================================ */
@page {
  size: A4 portrait;
  margin: 14mm;
}

@media print {
  /* Kontrollraden och varje knapp bort (§9.6) - en avstängd kolumn är redan borta ur DOM:en
     (klienten, se initPrintToggles), så display: none räcker här utan att bryta mot regeln att en
     avstängd kolumn aldrig får GÖMMAS i stället för tas bort. */
  .printbar {
    display: none;
  }

  /* thead { display: table-header-group; } ÄR redan webbläsarens förval för <thead> - regeln
     skrivs ändå (§9.6 namnger den uttryckligen) för att LÅSA fast egenskapen mot en framtida
     display-ändring på elementet, inte för att den saknas i dag. */
  .printtable thead,
  .printcomptable thead {
    display: table-header-group;
  }

  /* Ingen sidbrytning inuti en rad (§9.6). */
  .printtable tbody tr,
  .printcomptable tbody tr {
    break-inside: avoid;
  }

  /* Ingen regel som skriver ut länkadresser i sidfoten läggs till här: den utskrivna sidan
     innehåller inga <a>-element alls (rankande URL renderas som text, Utskrift.cshtml) - det
     finns alltså ingen href att lyfta ut som text, och avsaknaden är avsiktlig, inte bortglömd. */

  /* F84:s lucka (.gapdot) bärs annars ENBART av background (rad ~1454) - bakgrundsgrafik skrivs
     inte ut med webbläsarens standardinställning, så luckan blir på papperet visuellt identisk
     med KindMeasuring-cellens avsiktliga tomhet. En kant (border, som webbläsare skriver ut även
     med bakgrundsgrafik av) ger luckan sin egen, ihåliga form i stället - skild från den tomma
     mätcellen, §8.12s krav. Storleken (9×9, inte positionskolumnens 6×6) skiljer den DESSUTOM från
     .cell-pos .gapmark - samma kant på samma storlek som gapmark hade gjort F84:s konkurrentlucka
     och den egna kolumnens uteblivna mätdag omöjliga att skilja åt utan färg (§9.6). */
  .gapdot {
    width: 9px;
    height: 9px;
    background: transparent;
    border: 1px solid var(--text-faint);
  }

  /* §9.6: ledarmarkeringen bärs ENBART av ▲-glyfen (.tri), aldrig av tonen. Skärmens
     .printcomptable td.leader sätter background/box-shadow/border-radius för skärmen - i en
     border-collapse-tabell på papperet blir samma deklarationer i stället en fristående rundad ram
     tvärs radens kollapsade underkant. Print-blocket nollar dem explicit. */
  .printcomptable td.leader {
    background: none;
    box-shadow: none;
    border-radius: 0;
  }
}
