/* CAS1NO design-kit v2 — tokens + komponenter (server-renderad, no-JS) */
:root {
  /* ── Bastokens: obsidian och cyan (UX-04, kanon Chromatic Atlas §4.1) ──
     Historik i tre steg. (1) Fram till 2026-09-27 var det här GitHubs mörka
     tema, värde för värde: #0b0f14, #58a6ff, #3fb950, #d29922, #f85149,
     #e6edf3, #8b949e — en igenkännbar hyllpalett. (2) En första UX-04-omgång
     bytte ut den mot en egen, HANDUPPFUNNEN obsidian/violett-palett
     (#07080f/#b07cff m.fl.) istället för att läsa av kanons egen literal-
     tabell i docs/design/CHROMATIC_ATLAS.md §4.1 — och behöll dessutom en
     violett --accent trots kanons uttryckliga spärr mot precis det:
     docs/production/ATGARDSPLAN_VARLDSKLASS_2026-09-26.sv.md under "UX-04"
     stavar ut att violet.500 #6C39FF mäter 3.45:1 mot obsidian.900
     #080A12 — under 4.5-kravet för länktext. (3) Det här är rättningen:
     tokens nedan är kanons §4.1-värden ordagrant, inte en tredje egen
     tolkning. --accent är cyan.400 #37D8FF (samma värde som redan fanns
     som --chroma-cyan, interpolerat härifrån tokens.ts istället för
     handskrivet en andra gång) — violett förblir ren brand/dekoration
     (--chroma-violet/--chroma-violet-hyper, atmosfär, entity-DNA), aldrig
     bastoken.

     Kontrasten är RÄKNAD, inte bedömd. WCAG-kvot för varje par som bär text,
     mot den handuppfunna palett som byttes ut (steg 2 ovan):

       par                  steg 2   kanon §4.1
       text / bg            16.81    18.62
       text / bg-raised        —     17.89
       text / bg-card        15.12   17.14
       text-muted / bg        7.88   10.59
       text-muted / bg-raised   —    10.17
       text-muted / bg-card     —     9.75
       accent / bg             6.83  11.70
       accent / bg-raised        —   11.24
       accent / bg-card        6.14  10.77   ← steg 2:s sämsta textpar
       accent-strong / bg-card   —   13.18
       good / bg-card         13.04  13.20
       warn / bg-card         12.99  13.15
       bad / bg-card           7.19   7.28   ← kanonpalettens sämsta textpar
       good / bg                 —   14.34
       warn / bg                  —  14.28
       bad / bg                   —   7.91

     Kanonpalettens sämsta textpar (7.28) är bättre än både steg 2:s (6.14)
     och GitHub-basens ursprungliga (5.16). src/web/__tests__/
     palette-contrast.test.ts räknar om kvoterna ur de här tokens vid varje
     körning och fäller under 4.5, så en framtida justering inte kan glida
     under gränsen tyst. */
  --bg: #080a12; --bg-raised: #0d101a; --bg-card: #111522; --border: rgba(255, 255, 255, 0.08);
  --text: #f7f8fc; --text-muted: #b7becc; --accent: #37d8ff; --accent-strong: #8ce9ff;
  --good: #58f6b2; --warn: #ffd84a; --bad: #ff7a8a;
  /* Semantiska alias (PROMPT 14/ADR-0022) — samma värden som good/warn/bad
     idag, en egen namngiven roll för framtida kod som vill skriva
     "critical"/"warning"/"success" istället för att komma ihåg att bad=
     critical. focus/selected pekar idag på accent (samma visuella resultat
     som innan denna ändring) men är nu en egen CSS-egenskap att skifta
     oberoende av länkfärgen om det någonsin behövs. */
  --focus: var(--accent); --selected: var(--accent);
  --critical: var(--bad); --warning: var(--warn); --success: var(--good);
  /* UX-43: betyg och rank är inte varningar — egna roller (kanon §4.3/§15.4). */
  --rating: var(--chroma-yellow); --rank: var(--chroma-violet-hyper);
  /* UX-59: kommersiell markering får ALDRIG ärva brandens/signalernas visuella
     språk (kanon §1.6) — icke-semantisk, icke-spektral grafit utanför chroma/signal/dna. */
  --sponsor: #9aa4b8; --sponsor-bg: rgba(154, 164, 184, 0.12);
  --radius: 10px;
  /* --space-0 (token-städning): konsoliderar de tidigare ~10 näst-identiska
     mikro-padding-litteralerna (0.05/0.1/0.15/0.2rem) som badge/chip/
     kind-chip/filter-chip/compare-token vardera satte separat för sin
     vertikala padding — en enda källa till sanning istället för fem. */
  --space-0: 0.15rem;
  --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 1rem; --space-4: 1.5rem; --space-5: 2.5rem;
  /* UX-42 (kanon §7.1): den fulla 13-stegs spacingskalan. De befintliga
     namnen (--space-0…-5) står kvar oförändrade som alias (0/4/8/16/24/40px);
     här läggs hela kanon-skalan (4/8/12/16/20/24/32/40/48/64/80/96/128 px) till under
     prefixet --sp-* — MEDVETET inte --space-4/8/…, eftersom --space-4 redan finns
     (1.5rem) och ett återbrukat namn hade skuggat den tyst. Alla som rem (1rem=16px). */
  --sp-4: 0.25rem; --sp-8: 0.5rem; --sp-12: 0.75rem; --sp-16: 1rem;
  --sp-20: 1.25rem; --sp-24: 1.5rem; --sp-32: 2rem; --sp-40: 2.5rem;
  --sp-48: 3rem; --sp-64: 4rem; --sp-80: 5rem; --sp-96: 6rem; --sp-128: 8rem;
  /* UX-42 (kanon §7.2): basbredd 76rem (1216px); bred behållare 96rem (1536px)
     för jämförelse- och adminytor via .wrap--wide (appliceras i layout.page()). */
  --maxw: 76rem; --maxw-wide: 96rem;

  /* ── Typografi: tre röster och en tiostegsskala (Chromatic Atlas §6.1-6.2) ──
     Före det här blocket hade hela plattformen ETT typsnitt ("--font:
     system-ui"), noll "clamp()", 22 godtyckliga "font-size"-värden vars
     största var 2rem, och ingen bas-regel för "h1" alls — 63 av 64 "<h1>"
     ärvde webbläsarens 2em. Kanon kräver 56-80 px för H1 på desktop. Det är
     inte en smakskillnad, det är skillnaden mellan formgiven och oformgiven.

     ROSTERNA. Kanon §6.1 säger uttryckligen att "the exact licensed font may
     be chosen later, but the proportions are mandatory". Ingen licensierad
     face laddas här: det är ett inköpsbeslut, och en webbfont från tredje
     part vore dessutom fel för en EU-riktad sajt som annars hostar allt
     själv. Stackarna nedan väljer systemets EGNA display- respektive
     text-optiska snitt där de finns (Segoe UI Variable på Windows 11,
     -apple-system optical sizing på Apple) och faller tillbaka snyggt där de
     inte gör det. När en face licensieras är det EN rad per röst som ändras;
     allt annat i filen går redan genom de här tre tokens.

     SKALAN är flytande mellan 400 px och 1440 px viewport. Varje "clamp()"
     har en rem-term och inte bara vw — utan den skalar inte texten när
     besökaren zoomar, vilket bryter WCAG 1.4.4. Räknat så att minvärdet är
     kanons mobilmål och maxvärdet kanons desktopmål. */
  --font-display: "Segoe UI Variable Display", ui-sans-serif, system-ui,
    -apple-system, "Helvetica Neue", sans-serif;
  --font-ui: "Segoe UI Variable Text", system-ui, -apple-system, "Segoe UI",
    Roboto, "Helvetica Neue", sans-serif;
  --font-data: ui-monospace, "SF Mono", SFMono-Regular, "Cascadia Mono",
    "Segoe UI Mono", Consolas, "Liberation Mono", monospace;
  /* Bakåtkompatibelt alias — "--font" fanns före de tre rösterna och används
     fortfarande på ställen som bara menar "gränssnittstext". */
  --font: var(--font-ui);

  --type-display-xl: clamp(3.25rem, 0.654rem + 10.385vw, 10rem); /* 52 → 160 */
  --type-h1: clamp(2.375rem, 1.365rem + 4.038vw, 5rem); /*  38 → 80  */
  /* Sidtitel — ett medvetet avsteg från en bokstavlig läsning av §6.2, och
     det ska stå skrivet. Kanon anger 56-80 px för H1. På en redaktionell hero
     är det rätt. På /games, där rubriken är ordet "Spel" ovanför ett filter
     och en träfflista, blir 80 px en affisch, inte en produkt: titeln tar mer
     plats än det innehåll besökaren kom för. Bas-"h1" använder därför det här
     steget, och ".hero h1" använder kanons fulla H1. Ingen mall behöver ändras
     för det — heron har redan sin egen klass. */
  --type-page-title: clamp(1.75rem, 1.35rem + 1.6vw, 2.75rem); /* 28 → 44 */
  --type-h2: clamp(1.875rem, 1.25rem + 2.5vw, 3.5rem); /*   30 → 56  */
  --type-h3: clamp(1.5rem, 1.211rem + 1.154vw, 2.25rem); /* 24 → 36  */
  --type-h4: clamp(1.1875rem, 1.067rem + 0.481vw, 1.5rem); /* 19 → 24 */
  --type-body-l: 1.125rem; /* 18 */
  --type-body: 1rem; /* 16 */
  --type-small: 0.875rem; /* 14 */
  --type-micro: 0.8125rem; /* 13 — etiketter, aldrig instruktioner (§6.2) */
  --type-data: clamp(0.875rem, 0.779rem + 0.385vw, 1.125rem); /* 14 → 18 */

  /* Spårning. Det här är den enskilt största skillnaden mellan stor text som
     ser formgiven ut och stor text som ser ut som en default: optiska snitt
     sätts tätare ju större de blir, och utan negativ tracking ser 80 px
     systemtext uppspädd ut. Värdena är per steg, inte en global regel. */
  --track-display: -0.035em;
  --track-h1: -0.028em;
  --track-h2: -0.022em;
  --track-h3: -0.015em;
  --track-h4: -0.008em;
  --track-label: 0.07em;

  /* Radavstånd faller med storleken — 1.6 på brödtext är rätt, 1.6 på en
     80-pixelrubrik är tre gånger för luftigt. */
  --leading-display: 0.95;
  --leading-h1: 1.04;
  --leading-h2: 1.1;
  --leading-h3: 1.2;
  --leading-h4: 1.3;
  --leading-body: 1.6;
  /* CASIN0 Chromatic Atlas (ADR-0036) — Simon's locked visual direction.
     Kurerad spektralpalett, aldrig använd i sin helhet i en och samma vy
     (entity-dna.ts väljer ett stabilt par per entitet/sektion).

     Noten här sa tidigare att --bg/--text/--text-muted/--border ovan är
     OFÖRÄNDRADE och att de här tokens därför bara är dekoration. Den
     meningen är inte längre sann: UX-04 pekade om basen till kanon §4.1 —
     obsidian för bakgrunderna och cyan.400 som interaktionsaccent, INTE
     violett (kanon diskvalificerar uttryckligen violet.500 för länktext,
     se tokentabellens kommentar ovan). Violett lever kvar som ren
     brand/dekoration här nedan (--chroma-violet/--chroma-violet-hyper,
     atmosfär, entity-DNA) — chroma-tokens bär fortfarande aldrig brödtext,
     och kontrastkravet på dem är därför 3:1 för gränser och ikoner, inte 4.5. */
  --obsidian: #06070d; --chroma-violet: #6c39ff; --chroma-violet-hyper: #9a4dff;
  --chroma-pink: #ff2f9d; --chroma-coral: #ff566b; --chroma-orange: #ff8a32;
  --chroma-yellow: #ffd84a; --chroma-lime: #b8ff45; --chroma-mint: #58f6b2;
  --chroma-cyan: #37d8ff; --chroma-ice: #80b7ff;
}
/* Kompakt densitet (PROMPT 14/ADR-0022) — infrastruktur, ingen sida sätter
   ännu data-density="compact"; standardläget (ingen attribut-närvaro) är
   identiskt med v1:s spacing, så detta är additivt och kan inte regrediera
   någon befintlig sida. Halverar den vertikala rytmen för framtida täta
   ytor (adminlistor, jämförelsetabeller) utan att omdefiniera --space-*
   globalt. */
[data-density="compact"] {
  --space-2: 0.35rem; --space-3: 0.65rem; --space-4: 1rem; --space-5: 1.5rem;
}
/* Per-familj adaptivt tema (ADR-0028) — server-side, zero-JS data-family
   (games.family, en riktig aldrig-null kolumn, aldrig fabricerad) satt på
   <body> (spelets detaljsida) och på varje gameCard()-<article> (listor,
   liknande-spel). --accent kaskaderar via CSS custom properties till varje
   var(--accent)-användning i den underträdet (kort-hover, länkar, CTA:er)
   — ingen JS krävs. Grupperat per genre, inte en unik färg per taggnamn:
   katalogen är 99.87% slots (43186/43243, verifierad mot
   data/catalog/full-catalog.json denna session), så bara riktiga,
   existerande familjer i katalogen får en identitet; standardaccenten
   (slots, eller ingen [data-family]-matchning) är oförändrad. */
[data-family="live-casino"], [data-family="table-cards"],
[data-family="roulette"], [data-family="blackjack"],
[data-family="baccarat"], [data-family="poker"],
[data-family="video-poker"] {
  --accent: #2dd4bf; --accent-strong: #5eead4;
}
[data-family="lottery"], [data-family="bingo"],
[data-family="instant"], [data-family="sweepstakes"] {
  --accent: #e3b341; --accent-strong: #f2c94c;
}
[data-family="esports"], [data-family="crypto"],
[data-family="sports"], [data-family="social"],
[data-family="adjacent"], [data-family="venues"] {
  --accent: #818cf8; --accent-strong: #a5b4fc;
}
* { box-sizing: border-box; }
html { color-scheme: dark; scroll-padding-top: 3.5rem; scroll-padding-bottom: 64px; }
body { margin: 0; font-family: var(--font); background: var(--bg); color: var(--text);
  line-height: 1.6; font-size: var(--type-body); position: relative; isolation: isolate; }

/* ── Bas-rubriker ────────────────────────────────────────────────────────────
   Fram till nu fanns ingen enda "h1"-regel i den här filen. Varje rubrik som
   inte råkade ligga i ".hero" fick webbläsarens default: 2em, normal tracking,
   ärvd line-height 1.6. Det är därför sidorna såg ut som ett omarkerat
   dokument snarare än en produkt.

   Reglerna nedan är på elementen, inte på klasser, så de gäller alla 64 "<h1>"
   utan att någon sida behöver ändras. Komponenter som medvetet vill ha en
   mindre rubrik (kortrubriker, sektionshuvuden) sätter sin egen storlek efter
   det här — men går numera genom skalans tokens i stället för egna decimaler.

   "text-wrap: balance" gör att en tvåradig rubrik delas jämnt i stället för
   att lämna ett ensamt ord på rad två. Den ignoreras tyst där den inte stöds.
   "font-optical-sizing: auto" låter variabla systemsnitt välja sin display-
   variant vid stora grader, vilket är hela poängen med Segoe UI Variable. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  text-wrap: balance;
  color: var(--text);
}
h1 { font-size: var(--type-page-title); line-height: var(--leading-h2);
  letter-spacing: var(--track-h2); margin: 0 0 var(--space-3); }
h2 { font-size: var(--type-h2); line-height: var(--leading-h2);
  letter-spacing: var(--track-h2); margin: var(--space-5) 0 var(--space-2); }
h3 { font-size: var(--type-h3); line-height: var(--leading-h3);
  letter-spacing: var(--track-h3); margin: var(--space-4) 0 var(--space-2); }
h4 { font-size: var(--type-h4); line-height: var(--leading-h4);
  letter-spacing: var(--track-h4); margin: var(--space-3) 0 var(--space-1); }

/* Datarösten (kanon §6.1, tredje rösten). Siffror som ska JÄMFÖRAS måste stå
   i kolumn — proportionella siffror gör att 1 är smalare än 8, så två RTP-
   värden under varandra inte går att läsa av mot varandra utan att läsa dem.
   "tabular-nums" är den enda raden som behövs för det, och den fungerar i
   systemsnitten redan i dag, utan någon licensierad face. */
table, .data, .metric, .rtp, .pct, td, th,
.compare-table, .stat-value, .nav-live-count strong {
  font-variant-numeric: tabular-nums;
}

/* Etiketter i versalgemener behöver luft mellan tecknen — utan tracking blir
   versaler i små grader en grå klump (kanon §6.3). */
.eyebrow, .nav-eyebrow, .nav-group-heading, .footer-col-heading, .kind-chip,
th { letter-spacing: var(--track-label); }
/* Chromatic Atlas base atmosphere (ADR-0036 §3 + ADR-0081 våg 1) — the
   spectral haze fields + fine noise live on a dedicated decorative element
   (page() renders <div class="atmosphere" data-intensity="balanced"> right
   after the skip link) instead of body pseudo-elements: <body>/<html> must
   stay free of extra attributes because the density-/family-wheel tests pin
   exact tag prefixes. Fixed behind all content (z-index -2, pointer-events
   none — pure decoration, never intercepts input or reaches assistive tech).
   Body isolates these negative layers: its background paints first, the
   obsidian base at -3 next, then the visible haze at -2 below the content.
   Deliberately subtle: never competes with foreground legibility. Static —
   no animation. data-intensity=vivid/calm only shifts the field opacities;
   prefers-reduced-motion is unaffected because nothing animates. */
body::before {
  content: ''; position: fixed; inset: 0; z-index: -3; pointer-events: none;
  background: var(--obsidian);
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
/* UX-25 (2026-09-28): a single --focus ring is not guaranteed 3:1 against
   every surface it can land on (per-entity --dna-primary card borders/
   glows, --chroma-* accented surfaces like .badge.sponsor/.ad-slot) — a
   light ring can wash out on a light surface, a dark one on a dark one.
   Two concentric rings instead: an --obsidian inner ring (near-black,
   flush against the element with no offset) plus a --focus (accent) outer
   ring 4px out via box-shadow. One of the two always contrasts against
   whatever sits behind the focused element, so the ring is visible
   everywhere without inventing new tokens. */
:focus-visible { outline: 2px solid var(--obsidian); outline-offset: 0; box-shadow: 0 0 0 4px var(--focus); }
img, svg { max-width: 100%; }
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
/* accent (#58a6ff) vs text/text-muted both fall under the 3:1 axe requires for a link
   inline in a text block with no other distinguishing feature — underline it there. */
.muted { color: var(--text-muted); }
.muted a, .prose a { text-decoration: underline; }
/* Punktlös lista (t.ex. representant-badgen på /organizations/:id, P35). */
.plain { list-style: none; padding: 0; }
/* Tabulära siffror (designkontraktet) — .num är en fristående klass för
   valfri sifferyta utanför table.data/.rating-val/.compare-table (som
   redan hade sin egen tabular-nums sedan tidigare) att sätta explicit. */
.num { font-variant-numeric: tabular-nums; }
/* Sponsormarkering (PROMPT 14/ADR-0022) — en egen visuell roll, aldrig
   good/warn/bad, så en sponsrad platsering aldrig kan avläsas som ett
   kvalitetsomdöme. Ingen sida renderar denna än (annonsslot-rendering är
   PROMPT 28/39-46, se docs/agent/NEXT.md) — klassen finns redo när den ytan
   byggs, av samma skäl tokens byggs innan riktningen som använder dem. */
.badge.sponsor { color: var(--sponsor); border-color: var(--sponsor); }
.sponsor-tag { display: inline-block; padding: var(--space-0) var(--space-2); border-radius: 999px;
  font-size: var(--type-micro); background: var(--sponsor-bg); color: var(--sponsor); white-space: nowrap; }
.ad-slot { border-color: var(--sponsor); background: var(--sponsor-bg); margin-bottom: var(--space-3); }
.ad-slot h2 { margin: var(--space-1) 0 0; font-size: var(--type-body); }

.skip-link { position: absolute; left: -9999px; }
.skip-link:focus { left: var(--space-3); top: var(--space-3); background: var(--bg-raised);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-xs); z-index: var(--z-skip); }

/* Standard screen-reader-only pattern (not display:none, which hides it from AT too). */
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }

.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--space-3); }
.wrap--wide { max-width: var(--maxw-wide); }

/* Header */
.site-header { border-bottom: 1px solid var(--border); background: var(--bg-raised);
  position: sticky; top: 0; z-index: var(--z-header); }
.site-header .wrap { display: flex; align-items: center; gap: var(--space-4);
  min-height: 3.5rem; flex-wrap: wrap; padding-top: var(--space-1); padding-bottom: var(--space-1); }
/* CASIN0 Portal mark (ADR-0036 §2) — the trailing "O" becomes a small
   chromatic ring/portal glyph instead of plain text. Pure CSS/SVG, no
   WebGL, no JS: the ring's rotation on hover is a CSS transform on an
   inner <g>, degrades to a static ring under prefers-reduced-motion (the
   existing global reduced-motion rule already covers it). */
.logo { display: inline-flex; align-items: center; gap: 0.1rem; font-weight: 700;
  font-size: var(--type-body-l); letter-spacing: 0.05em; color: var(--text); }
.logo span { color: var(--accent); }
.logo-portal { flex-shrink: 0; }
.logo:hover .logo-portal-ring { transform: rotate(35deg); }
.header-search { margin-left: var(--space-3); }
.header-search input { background: var(--bg); border: 1px solid var(--border); color: var(--text);
  border-radius: var(--radius-xs); padding: 0.35rem 0.7rem; min-width: 12rem; font-size: var(--type-small); }

/* Sticky compact entity header (ADR-0056) — game/casino detail pages only.
   Neither page has a .hero block to anchor on (that class is homepage/
   prototype-only, see ADR-0056) — this small bar sits in-flow right after
   the entity's own h1 and becomes sticky once scrolled past, so the
   entity's name (and, for casinos, a real compare link) stays visible.
   Stacked as a *second* sticky layer below the already-sticky
   .site-header (top:0, header-lagret via --z-header, min-height 3.5rem — line 138 above)
   rather than at top:0 itself, so the two never overlap: top here equals
   the header's own real height, and z-index (40) sits below the header
   (50) and below the nav-overlay (60) so an open nav menu always wins.

   UX-12 correction (2026-09-28): .entity-sticky-bar and .quick-nav
   (below) used to each be their OWN independent 'position: sticky'
   element with a different 'top' offset, on the theory that two sticky
   siblings would naturally stack one below the other. They don't — two
   independent sticky elements only stay clear of each other while their
   shared containing block (the page's content <section>) is tall enough
   for both to be simultaneously "stuck" without either running out of
   room; on a real, data-sparse casino (no licenses, no review, no
   bonuses) the section is short enough that .quick-nav's sticky range
   collides with .entity-sticky-bar's, and .quick-nav (z-index 39) renders
   fully hidden behind .entity-sticky-bar (z-index 40) — confirmed via a
   real rendered layout check (getBoundingClientRect(), real Chromium): a
   37px overlap starting around scrollY 260-300px on a 1280px viewport.
   Fixed at the root instead of tuning offsets again: .entity-sticky-group
   (web/pages.ts) wraps both as ONE sticky unit — the bar and the nav are
   now plain, non-sticky children stacked in normal flow *inside* it, so
   there is only ever one sticky element in this region and nothing for
   it to collide with. */
.entity-sticky-group { position: sticky; top: 3.5rem; z-index: var(--z-sticky); }
.entity-sticky-bar { background: var(--bg-raised);
  border: 1px solid var(--border); border-radius: var(--radius-xs); padding: 0.4rem var(--space-3);
  margin: 0 0 var(--space-2); font-size: var(--type-small); font-weight: 600; display: flex; align-items: center;
  gap: var(--space-2); flex-wrap: wrap; }
.entity-sticky-bar-meta { font-weight: 400; color: var(--text-muted); font-size: var(--type-small); }
.entity-sticky-bar-action { margin-left: auto; font-size: var(--type-small); }

/* Primary nav (ADR-0040 §L0/L1) — a stable, always-visible top-level bar
   for the platform's real entity areas (Games/Casinos/Providers/Bonuses),
   never reordered by fresh content — dynamism lives inside the existing
   nav-overlay mega-menu and page content, not here. Hidden below the
   breakpoint where four links plus search plus the hamburger would crowd
   a phone-width header; the hamburger overlay (ADR-0035) stays the sole
   nav entry point there, unchanged. */
.primary-nav { display: none; }
@media (min-width: 48rem) {
  .primary-nav { display: flex; align-items: center; gap: var(--space-3); }
  .primary-nav a { color: rgba(255,255,255,.65); font-size: var(--type-small); font-weight: 600;
    padding: 0.3rem 0; border-bottom: 2px solid transparent; transition: color var(--motion-micro) var(--ease-out-spectral), border-color var(--motion-micro) var(--ease-out-spectral); }
  .primary-nav a:hover { color: var(--text); }
  .primary-nav a[aria-current="page"] { color: var(--text); border-bottom-color: var(--dna-primary, var(--accent)); }
}

/* Section secondary navigation (ADR-0055) — a quieter second-tier row
   under .primary-nav, shown only on pages inside one of the four real
   catalog sections (secondaryNavFor() in layout.ts). Sits in normal flow
   directly under .site-header. Deliberately smaller/muted relative to
   .primary-nav — a subordinate signal, not a second main bar — and hidden
   below the same 48rem breakpoint: no new nav surface reaches mobile, the
   hamburger overlay (ADR-0035) stays the sole entry point there. */
.secondary-nav { display: none; }
@media (min-width: 48rem) {
  .secondary-nav { display: block; border-bottom: 1px solid var(--border); background: var(--bg); }
  .secondary-nav .wrap { display: flex; align-items: center; gap: var(--space-3);
    min-height: 2.1rem; padding-top: 0.3rem; padding-bottom: 0.3rem; }
  .secondary-nav a { color: var(--text-muted); font-size: var(--type-small); font-weight: 500;
    padding: 0.15rem 0; border-bottom: 2px solid transparent;
    transition: color var(--motion-micro) var(--ease-out-spectral), border-color var(--motion-micro) var(--ease-out-spectral); }
  .secondary-nav a:hover { color: var(--text); }
  .secondary-nav a[aria-current="page"] { color: var(--text);
    border-bottom-color: var(--dna-primary, var(--accent)); }
}

/* Nav overlay (ADR-0035) — zero-JS full-screen menu via <details>/<summary>,
   modeled on siax.io's own menu (real, native HTML disclosure semantics:
   keyboard-operable, and screen readers get open/closed state for free —
   no ARIA or JS needed to announce it). */
.nav-menu { margin-left: auto; position: relative; }
.nav-menu-toggle { display: flex; align-items: center; gap: var(--space-2);
  cursor: pointer; list-style: none; padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-xs); color: var(--text); user-select: none; }
.nav-menu-toggle::-webkit-details-marker { display: none; }
.nav-menu-toggle::marker { content: ''; }
.nav-menu-toggle:hover { background: var(--bg-card); }
.nav-menu-icon { display: inline-flex; flex-direction: column; justify-content: center;
  gap: 4px; width: 20px; }
.nav-menu-icon span { display: block; height: 2px; background: currentColor; border-radius: 1px;
  transition: transform var(--motion-micro) var(--ease-out-spectral), opacity var(--motion-micro) var(--ease-out-spectral); }
.nav-menu[open] .nav-menu-icon span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-menu[open] .nav-menu-icon span:nth-child(2) { opacity: 0; }
.nav-menu[open] .nav-menu-icon span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
.nav-menu-label-close { display: none; }
.nav-menu[open] .nav-menu-label-open { display: none; }
.nav-menu[open] .nav-menu-label-close { display: inline; }
/* Level 3 Prism Glass (kanon §8.2/§8.7) — see the material & depth token
   block below for the BEFORE_STATE and rationale. -webkit- prefix kept
   alongside the unprefixed property for Safari, which still requires it. */
.nav-overlay { position: fixed; inset: 3.5rem 0 0; z-index: var(--z-overlay); background: var(--depth-3-bg);
  -webkit-backdrop-filter: blur(var(--depth-3-blur)); backdrop-filter: blur(var(--depth-3-blur));
  box-shadow: inset 0 1px 0 var(--depth-3-sheen), var(--elev-overlay);
  overflow-y: auto; border-top: 1px solid var(--depth-3-border); }
/* Deliberately NOT the shared .wrap class (real bug found + fixed this pass):
   .nav-overlay-inner sits inside .site-header in the DOM (position:fixed only
   changes paint position, not CSS descendant-selector matching), so reusing
   .wrap here also matched the higher-specificity ".site-header .wrap" flex
   rule and silently turned every child into a wrapping flex item — the
   actual cause of a "grid" that rendered as a half-width, offset column no
   matter what grid-template-columns value was set. Same max-width/centering
   intent as .wrap, redeclared under its own class name instead. */
.nav-overlay-inner { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--space-3);
  padding-top: var(--space-5); padding-bottom: var(--space-5); }
.nav-eyebrow { text-transform: uppercase; letter-spacing: 0.08em; font-size: var(--type-micro);
  color: var(--accent); margin: var(--space-4) 0 var(--space-1); }
.nav-eyebrow:first-child { margin-top: 0; }
.nav-overlay-heading { font-size: var(--type-h3); margin: 0 0 var(--space-4); }
/* Explicit, media-query-driven column counts rather than
   repeat(auto-fit, minmax(...)) — a deliberate, simple choice for a short,
   known list (never data-driven), not a workaround for anything broken. */
.nav-areas-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-3);
  margin-bottom: var(--space-4); }
.nav-area-card { display: flex; flex-direction: column; gap: var(--space-1);
  padding: var(--space-3); background: var(--bg-card); border: 1px solid var(--border);
  border-radius: var(--radius); color: var(--text);
  transition: border-color var(--motion-card) var(--ease-out-spectral), box-shadow var(--motion-card) var(--ease-out-spectral); }
.nav-area-card:hover, .nav-area-card[aria-current="page"] { border-color: var(--dna-primary, var(--accent));
  box-shadow: 0 0 28px -10px color-mix(in srgb, var(--dna-primary, var(--accent)) 40%, transparent);
  text-decoration: none; }
.nav-area-num { font-size: var(--type-micro); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.nav-area-card strong { font-size: var(--type-body-l); }
.nav-area-desc { color: var(--text-muted); font-size: var(--type-small); }
/* UX-18/UX-19: browse-zonen är grupperad, inte en platt länklista. Grupperna
   kommer ur nav-registry.ts, så kolumnerna kan inte hamna i otakt med vad
   navigationen faktiskt länkar. Varje grupp är sin egen <nav> med rubriken som
   aria-label, så en skärmläsare kan hoppa mellan dem i stället för att ta sig
   igenom trettio länkar i rad. Graderna kommer ur typskalan (UX-05) — ett eget
   decimalvärde här fälls av src/web/__tests__/type-scale.test.ts. */
.nav-groups { display: grid; grid-template-columns: 1fr; gap: var(--space-3) var(--space-4); }
.nav-group { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.nav-group-heading { text-transform: uppercase; letter-spacing: var(--track-label);
  font-size: var(--type-micro); color: var(--text-muted); opacity: 0.75;
  margin: 0 0 var(--space-1); }
.nav-group a { color: var(--text-muted); padding: var(--space-1) 0;
  border-left: 2px solid transparent; padding-left: var(--space-2);
  margin-left: calc(var(--space-2) * -1); }
.nav-group a:hover, .nav-group a:focus-visible, .nav-group a[aria-current="page"] {
  color: var(--text); text-decoration: none;
  border-left-color: var(--entity-accent, var(--accent)); }
@media (min-width: 30rem) {
  .nav-areas-grid { grid-template-columns: repeat(2, 1fr); }
  .nav-groups { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 56rem) {
  .nav-areas-grid { grid-template-columns: repeat(4, 1fr); }
  .nav-groups { grid-template-columns: repeat(4, 1fr); }
}
@media (max-width: 40rem) {
  .nav-overlay { inset: 3.25rem 0 0; }
  /* Matches the header's own shorter height at this breakpoint (nav-overlay's
     inset switch above uses the same 3.25rem value) — keeps the two sticky
     layers flush with no gap or overlap on small screens. */
  .entity-sticky-group { top: 3.25rem; }
  /* UX-25: html's base scroll-padding-top (3.5rem) targets the desktop
     header height; at this breakpoint the header itself is shorter
     (3.25rem, same value as the two rules above), so a keyboard-focused
     #anchor target needs the smaller offset too or it lands with its top
     edge under the header. */
  html { scroll-padding-top: 3.25rem; }
}

/* Hero + sektioner */
.hero { padding: var(--space-5) 0; border-bottom: 1px solid var(--border); }
/* Heron är den redaktionella ytan, så den — och bara den — bär kanons fulla
   H1-grad. Den satte tidigare 2rem, vilket var under kanons MOBILmål. */
.hero h1 { margin: 0 0 var(--space-2); font-size: var(--type-h1);
  line-height: var(--leading-h1); letter-spacing: var(--track-h1); }

/* Ingressen: stycket direkt efter en sidtitel är per definition ingressen, och
   kanon §6.2 ger den Body L (18 px), inte brödtext. Syskonväljaren gör det utan
   att en enda mall behöver en ny klass. */
h1 + p, .hero p { font-size: var(--type-body-l); line-height: var(--leading-body);
  max-width: 68ch; }
.hero p { color: var(--text-muted); max-width: 46rem; margin: 0 0 var(--space-3); }
section { padding: var(--space-4) 0; }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3);
  flex-wrap: wrap; margin-bottom: var(--space-3); }
.section-head h2 { margin: 0; font-size: var(--type-h3);
  line-height: var(--leading-h3); letter-spacing: var(--track-h3); }
.section-head a { font-size: var(--type-small); }

/* Statkort */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--space-3); }
.stat { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--space-3); }
.stat strong { display: block; font-size: var(--type-h3); color: var(--accent-strong); font-variant-numeric: tabular-nums; }
.stat span { color: var(--text-muted); font-size: var(--type-small); }

/* Kortgrid */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: var(--space-3); }
.card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-1);
  transition: border-color var(--motion-card) var(--ease-out-spectral), box-shadow var(--motion-card) var(--ease-out-spectral), transform var(--motion-card) var(--ease-out-spectral); }
.card:hover { border-color: var(--accent); }
/* Entity Color DNA (ADR-0036 §5, entity-dna.ts) — each individual game/
   provider gets its own stable --dna-primary/--dna-secondary (set inline
   per-card, server-rendered). Overrides the plain .card:hover border with
   the entity's own identity color instead of the shared --accent, while
   still sitting on top of ADR-0028's per-family section theming (this is a
   finer, per-object layer, not a replacement for it). */
.card-dna:hover { border-color: var(--dna-primary); box-shadow: 0 0 32px -8px color-mix(in srgb, var(--dna-primary) 45%, transparent);
  transform: translateY(-2px); }
.card h2 { margin: 0; font-size: var(--type-body); }
.card h2 a { color: var(--text); }
/* Media-ytan (spelbilder, crawl-bot/media): card-thumbnail + detalj-hero.
   Ärligt frånvarande (inget <img> renderas) när tillgången saknas i det
   lokala biblioteket — ingen extern hotlink, ingen placeholder-bild. */
.card-media { display: block; margin: 0 0 var(--space-1); aspect-ratio: 3/2; overflow: hidden;
  border-radius: calc(var(--radius) - 2px); background: var(--bg); }
.card-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.detail-media { margin: 0 0 var(--space-3); max-width: 420px; }
.detail-media img { width: 100%; height: auto; border-radius: var(--radius);
  border: 1px solid var(--border); display: block; }
.card .meta { color: var(--text-muted); font-size: var(--type-small); display: flex; gap: var(--space-2); flex-wrap: wrap; }
/* UI-01: leverantören i kortets metarad är en länk när frågan har bevisat att
   organisationen finns. Den ska SE ut som en länk — en klickbar yta som ser
   likadan ut som texten bredvid är en yta ingen klickar på. Understrykningen
   ligger på offset så den inte skär genom bokstavsstaplarna, och träffytan
   följer UX-17:s minimimått via metaradens egen padding. */
.card .meta a.meta-link { color: var(--text-muted); text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--dna-primary, var(--accent)) 55%, transparent);
  text-underline-offset: 0.22em; text-decoration-thickness: 1px; }
.card .meta a.meta-link:hover, .card .meta a.meta-link:focus-visible {
  color: var(--text); text-decoration-color: var(--dna-primary, var(--accent)); }
.card .foot { margin-top: auto; display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap;
  font-size: var(--type-small); color: var(--text-muted); }
/* Card expand-in-place (UX-audit 2026-09-27) — a real drill-down for
   casinoCard() (components.ts) via the SAME zero-JS <details>/<summary>
   disclosure already proven twice in this file: the full-screen nav
   overlay (ADR-0035, .nav-menu above) and .faq-accordion (below). Native
   disclosure semantics give a screen reader open/closed state for free —
   no ARIA, no JS. Generic on .card rather than casino-specific: the class
   names carry no casino-only meaning, so a later card type can reuse this
   exact box instead of a parallel one. list-style: revert keeps the
   browser's own disclosure triangle — same choice .faq-accordion summary
   already made below (there is no custom icon here to replace it with,
   unlike .nav-menu-toggle's hamburger). The collapsed (closed) state adds
   only this one summary row — every field that rendered before this pass
   (title, .meta, .foot) is untouched. */
.card-expand { margin-top: var(--space-1); }
.card-expand-toggle { cursor: pointer; list-style: revert; color: var(--text-muted);
  font-size: var(--type-small); padding: var(--space-2) 0; }
.card-expand-toggle:hover, .card-expand-toggle:focus-visible { color: var(--text); }
.card-expand-label-close { display: none; }
.card-expand[open] .card-expand-label-open { display: none; }
.card-expand[open] .card-expand-label-close { display: inline; }
/* Samma .detail-grid/.kv-yta som detaljsidans faktatabell (:637-642 nedan)
   — inget nytt paneljärgspråk. Tätare marginal än .detail-grid:s eget
   (var(--space-3)) eftersom ytan redan sitter inne i ett kort, samma
   omtoning som .case-header .detail-grid gör av samma skäl.
   grid-template-columns: 1fr (i stället för att ärva .detail-grid:s
   repeat(auto-fit, minmax(14rem, 1fr))) — verifierat med en riktig
   skärmdump mot /providers på 1280px: .grid:s egna kort kan vara så
   smala som 13rem (:482), smalare än .detail-grid:s 14rem-golv, så
   varje kv-rad tvingades bredare än sitt eget kort och svämmade in i
   grannkortet. En enda kolumn är dessutom rätt layout i ett redan
   smalt kort — det finns sällan plats för två 14rem-kolumner bredvid
   varandra där ändå. Delad klass — samma fix täcker nu gameCard()'s
   expand-panel också, verifierat med samma metod mot /games (se
   game-card-expand.test.ts + PR-beskrivningen). */
.card-expand-panel.detail-grid { grid-template-columns: 1fr; margin: var(--space-2) 0 0; }
.card-expand-panel .kv { padding: var(--space-2); }
.card-expand-panel .kv dd { overflow-wrap: break-word; }
/* /regulators row drill-down (regulator-row-drilldown): the same
   .card-expand/.card-expand-toggle open-close affordance above, nested in
   a plain <td> instead of an <article class="card">. The panel itself is a
   flat link list, not the key/value .detail-grid casinoCard() uses — a
   regulator's licensed casinos are names, not fields — so it gets its own,
   much smaller reset instead of forcing .detail-grid's grid-template-columns
   (a minmax(14rem, …) floor) into a table cell; that exact floor is what
   overflowed .card-expand-panel in a narrow card once already (:550-555
   above). max-width bounds how far one open row can widen its shared table
   column — real regulators run to ~50 licensed casinos (see
   regulatorRow()'s doc comment, components.ts), never rendered all inline,
   hence the capped preview + "+N more" link to the same /regulators/:id
   page instead of growing the cell without bound. */
.row-expand-panel { list-style: none; margin: var(--space-2) 0 0; padding: 0;
  max-width: 16rem; text-align: left; }
.row-expand-panel li + li { margin-top: 0.2rem; }
.row-expand-panel a { overflow-wrap: break-word; }
/* Genererade initial-avatarer (ADR-0030) — casinon/providers saknar riktiga
   logotyper (se ADR-0028); samma mönster som Gmail/Slack/GitHub för en
   entitet utan foto. avatar-svg krymper aldrig sin flex-container. */
.avatar { flex-shrink: 0; border-radius: 22%; vertical-align: middle; }
.entity-media-avatar { object-fit: cover; background: var(--bg-raised); }
.card-title, .entity-title, .row-name { display: flex; align-items: center; gap: var(--space-2); }
.entity-title { margin: 0 0 var(--space-2); }
.row-name a { display: inline-block; }
/* UX-51: avatarWithBadge() (web/avatar.ts) — a small corner badge overlaid
   on the generated avatar (userReviewCard/memberProfilePage). .avatar-wrap
   is a plain positioning box around .avatar's own flex-shrink:0 sizing
   (never sized itself, so it never fights the SVG's own width/height
   attrs); .avatar-badge is deliberately small and border-matched to
   --bg-card so it reads as a corner mark, not a second icon competing
   with the initials. Reuses .badge's existing 'good' color token — never
   a new color vocabulary for the same "verified" meaning bonusCard()'s
   own '.badge.good' check already carries elsewhere on the same card. */
.avatar-wrap { position: relative; display: inline-flex; flex-shrink: 0; line-height: 0; }
/* The badge glyph uses --type-micro, the type scale's own smallest step
   (kanon §6.2, enforced by type-scale.test.ts) — never a bespoke size; the
   badge box itself is sized a touch larger than that step (not a type
   token, just a box) so the glyph doesn't clip its circle. */
.avatar-badge { position: absolute; right: -0.25rem; bottom: -0.25rem; display: inline-flex;
  align-items: center; justify-content: center; width: 1.1rem; height: 1.1rem; border-radius: 50%;
  background: var(--bg-card); border: 1px solid var(--good); color: var(--good);
  font-size: var(--type-micro); line-height: 1; }

/* Badge + chips */
.badge { display: inline-block; padding: var(--space-0) var(--space-2); border-radius: 999px; font-size: var(--type-micro);
  border: 1px solid var(--border); color: var(--text-muted); white-space: nowrap; }
.badge.good { color: var(--good); border-color: var(--good); }
.badge.warn { color: var(--warn); border-color: var(--warn); }
.badge.bad { color: var(--bad); border-color: var(--bad); }
.badge.accent { color: var(--accent); border-color: var(--accent); }
/* WP-0.07: the "Orimlig uppgift (insats ≈ …)" flag is a sentence, not a one-word chip. A nowrap chip
   that long widens a stacked mobile table row past the viewport (axe: scrollable-region-focusable on
   the game page's wins table at 390px), so this one is allowed to wrap. */
.badge.implausible { white-space: normal; text-align: left; border-radius: var(--radius-xs); line-height: 1.25; }
/* Jackpott är en egen visuell identitet (ADR-0028) — tidigare .badge.warn
   (samma amber som en generisk varning), vilket sammanblandade "det finns
   en jackpott" med "något behöver granskas". Guld/gradient, aldrig samma
   ton som --warn. */
.badge.jackpot { color: #1a1500; background: linear-gradient(135deg, #ffd60a, #f0b429);
  border-color: #f0b429; font-weight: 700; }
.chip-row { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-2) 0; }
.chip { display: inline-block; padding: var(--space-0) 0.7rem; border-radius: 999px; font-size: var(--type-small);
  border: 1px solid var(--border); color: var(--text-muted); }
.chip:hover { border-color: var(--accent); color: var(--text); text-decoration: none; }
/* aria-current="page" är den semantiskt riktiga markören för en aktiv länk i en
   chip-rad som byter sida (compare-typväxlaren, /games vyväxlare); "true" är
   växlare som bara markerar ett läge. Båda ska synas för seende användare. */
.chip[aria-current="true"], .chip[aria-current="page"] { background: var(--selected); color: var(--bg); border-color: var(--selected); }

/* ADR-0059 anchor quick-nav — #anchor jump targets on /games/:slug, extended
   to /casinos/:slug by ADR-0061 (same pattern, same targets-are-real
   discipline). Built entirely from --space-* tokens already in use
   elsewhere (not a new magic number): --space-5 (2.5rem) + --space-4
   (1.5rem) = 4rem, just past .site-header's real rendered height (3.5rem
   min-height + 2×--space-1 padding + 1px border ≈ 4rem), so a #anchor jump
   lands the target's own top edge clear of the sticky header instead of
   underneath it. #casino-trust added by UX-12 (2026-09-27): it became a
   real quick-nav target (see casinoDetailQuickNav(), web/components.ts) and
   needs the exact same clearance as every other anchor target on this
   list — no new magic number here either. */
#game-facts, #game-similar, #game-recently-viewed, #game-wins,
#game-availability, #game-claims,
#casino-licenses, #casino-review, #casino-user-reviews, #casino-bonuses,
#casino-trust {
  scroll-margin-top: calc(var(--space-5) + var(--space-4));
}

/* UX-12 (2026-09-27, corrected 2026-09-28): the quick-nav itself was never
   actually sticky — live-verified via a real rendered layout check
   (getBoundingClientRect()) that it scrolled away with the rest of the
   page instead of staying reachable. It first got its OWN independent
   'position: sticky' (a calc()'d 'top' meant to stack it below
   .entity-sticky-bar) — measured to be wrong on a data-sparse casino,
   where the shared containing block is short enough that the two
   independent sticky elements collide instead of staying stacked (see
   .entity-sticky-group's comment above for the full failure mode and its
   fix). .quick-nav is now a plain, non-sticky block: it doesn't need its
   own 'top'/z-index any more because .entity-sticky-group above it is the
   only sticky element in this region — .quick-nav simply sits in normal
   flow directly under .entity-sticky-bar, inside that one sticky wrapper.
   The background is still required: once the group is stuck, page content
   scrolling underneath would otherwise show through the gaps between the
   chip pills. */
.quick-nav { background: var(--bg); padding: var(--space-1) 0; }

/* Filterrad + tabell */
.filterbar { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center;
  margin: var(--space-3) 0; font-size: var(--type-small); }
.filterbar label { color: var(--text-muted); }
.filterbar select { background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--radius-xs); padding: 0.3rem 0.5rem; }
/* .filterbar .apply's own default/hover/focus/active/disabled rules moved to
   the unified .btn system below (UX-50) — it's folded into that shared
   selector list as an alias of .btn--primary instead of duplicating the
   same values here. No call site changes: this class name still resolves
   to the same look. */

/* regulator-row-drilldown: contain: layout paint (below) closes a real overflow
   leak found measuring /regulators at 320px (Playwright getBoundingClientRect
   + document.documentElement.scrollWidth, not eyeballed): with a <details>
   nested inside a <td> nested in here, the wrapped table's own intrinsic
   width (490px, measured) pushed document.body.scrollWidth to 448 at a
   320px viewport — a real page-level horizontal scrollbar — even though
   .table-wrap's OWN box correctly stayed at 288px and scrolled its content
   internally (confirmed: .table-wrap's reported scrollWidth was 490 either
   way, and casin0.io/regulators's existing 3-column table, no <details>,
   already relies on this same internal scroll at 320px without leaking).
   contain: layout paint isolates this box's internal layout/painting from
   the rest of the document — the standard fix for a scrollable container
   whose over-wide content bleeds into ancestor overflow — and doesn't change
   anything visible: .table-wrap already clips to its own box today, this
   just stops that clip from being undone one level up. Verified: with it,
   the same measurement is 320/320, closed AND with a row open, at 320/390/
   1280px — see scripts/a11y-check.mjs's /regulators row-drilldown check.

   UX-47 (2026-09-28): 'overflow-x: auto' itself is now read from the
   --table-wrap-overflow-x custom property (set below, only inside the
   stacked-mobile-rows media query at ≤640px) instead of applying at every
   width unconditionally — a custom property rather than a second
   '.table-wrap' rule so this stays the file's only declaration for that
   selector (the very duplication this comment block's neighbours were
   just merged out of — a second copy would only reintroduce the same
   maintenance hazard the merge fixed). Real, measured cause: 'overflow-x'
   set to anything other than 'visible' makes an element a scroll
   container for position: sticky purposes REGARDLESS of overflow-y or of
   whether any content actually overflows — confirmed with a real rendered
   layout check (Playwright, getBoundingClientRect() across a range of
   scroll positions, not eyeballed): a sticky 'thead th' inside this
   wrapper stayed pinned to .table-wrap's own top instead of the viewport,
   so it just scrolled away with the rest of the page — see the sticky-
   header rule below, which needs '.table-wrap' to NOT be that scroll
   container at the widths where a header actually stays visible. Narrowing
   the auto-scroll behaviour to ≤640px only is safe: every known real
   over-wide-content case this rule protects against (this comment's own
   320px regulator finding) is a narrow-viewport problem, and ≤640px
   already hides <thead> entirely for the stacked-rows layout, so there is
   no width where a visible, sticky header still needs '.table-wrap' to
   also be a horizontal-scroll container. 'contain: layout paint' alone
   (kept unconditional) does not trap position: sticky the same way —
   verified with the same real layout check. */
.table-wrap { overflow-x: var(--table-wrap-overflow-x, visible); contain: layout paint; }
/* UI-09: diagramfiguren. SVG:n bär sina egna färger genom tokens direkt i
   presentationsattributen, så det enda som behövs här är plats och en ram som
   säger att bilden hör ihop med talen ovanför. */
.chart { margin: var(--space-3) 0 0; padding: var(--space-3) 0 0;
  border-top: 1px solid var(--border); }
.chart svg { display: block; max-width: 100%; height: auto; }
.chart-caption { margin: 0 0 var(--space-2); color: var(--text-muted);
  font-size: var(--type-micro); text-transform: uppercase;
  letter-spacing: var(--track-label); }
table.data { width: 100%; border-collapse: collapse; font-size: var(--type-small); }
table.data th, table.data td { text-align: left; padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--border); }
table.data th { color: var(--text-muted); font-weight: 600; font-size: var(--type-micro); text-transform: uppercase;
  letter-spacing: 0.04em; }
/* UI-08: en sorterbar rubrik är en länk, och ska se ut som en yta man kan
   peka på. Hela cellen är klickbar (display:block + negativ marginal som
   äter cellens egen padding), så träffytan är kolumnens bredd och inte bara
   ordet — UX-17:s minimimått nås utan att cellen växer. Pilen bärs av den
   aktiva kolumnen; övriga rubriker byter bara färg vid hover, så raden inte
   ser ut som fem aktiva sorteringar. */
table.data th a.th-sort { display: block; color: inherit; text-decoration: none;
  margin: -0.5rem -0.7rem; padding: 0.5rem 0.7rem; white-space: nowrap; }
table.data th a.th-sort:hover, table.data th a.th-sort:focus-visible { color: var(--text); }
table.data th a.th-sort-active { color: var(--text); }
table.data th .th-sort-arrow { margin-left: 0.35em; color: var(--accent); }
table.data tr:hover td { background: var(--bg-raised); }
table.data td.num, table.data th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* UX-47 sticky table headers — table.data's own header row (above) and
   .compare-table's (merged declaration above, kanon §28.3/§19.4) both stay
   visible while a long table's body scrolls past underneath. 'top' is
   deliberately the exact same calc(var(--space-5) + var(--space-4)) the
   #game-facts/#casino-... anchor scroll-margin-top block already uses
   (further up this file) — one offset for 'below the site header', not two
   independently-tuned numbers that could drift apart the next time
   .site-header's own height changes. background is required for the same
   reason .quick-nav's is (see that rule's comment): once the header row is
   stuck, page content scrolling underneath it would otherwise show through.
   its local stacking only has to clear this table's own rows, nowhere near
   .entity-sticky-group's 40 or .site-header's 50. */
table.data thead th, .compare-table thead th {
  position: sticky;
  top: calc(var(--space-5) + var(--space-4));
  background: var(--bg);
  z-index: var(--z-content);
}

/* UX-47 stacked mobile table rows — below 640px a data table's own columns
   no longer fit a phone width without the '.table-wrap' horizontal scroll
   becoming the only way to read a row at all (confirmed: every table.data/
   .compare-table caller already wraps in '.table-wrap', so this never has
   to also guard against a bare, unwrapped table). Each <tr> becomes its own
   block-level "card" instead: the header row is hidden (nothing left for
   'position: sticky' above to do at this width either) and every <td>
   prints its own column name via a data-label attribute the row's own
   renderer sets — the same field name a desktop reader would see in
   <thead>, never a separate, second copy of that string. A <td> with no
   data-label (a control-only column such as the regulators drilldown
   toggle, or the collection-collaborators remove button, both already
   unlabelled '<th>' cells on desktop for the same reason) just prints no
   label here either, consistent with that existing pattern rather than a
   new one. */
@media (max-width: 640px) {
  /* UX-47: turns .table-wrap's overflow-x back on at this width (see the
     custom-property comment on the base '.table-wrap' rule further up) —
     the sticky-header rule above needs it off above 640px, and below
     640px <thead> is hidden anyway (nothing sticky left to trap). Set on
     :root, not '.table-wrap' itself, so this file keeps exactly one
     '.table-wrap' selector. */
  :root { --table-wrap-overflow-x: auto; }
  table.data, .compare-table { display: block; }
  table.data thead, .compare-table thead { display: none; }
  table.data tbody, .compare-table tbody { display: block; }
  table.data tr, .compare-table tr {
    display: block; padding: var(--space-2) 0;
    border-bottom: 1px solid var(--border);
  }
  table.data td, .compare-table td {
    display: flex; justify-content: space-between; align-items: baseline;
    gap: var(--space-3); padding: 0.25rem 0; border-bottom: 0; text-align: right;
  }
  table.data td::before, .compare-table td::before {
    content: attr(data-label); flex-shrink: 0; text-align: left;
    color: var(--text-muted); font-weight: 600; font-size: var(--type-micro);
    text-transform: uppercase; letter-spacing: 0.04em;
  }
}

/* Paginering + breadcrumb */
.pagination { display: flex; gap: var(--space-1); flex-wrap: wrap; margin: var(--space-4) 0;
  font-size: var(--type-small); }
.pagination a, .pagination span { padding: 0.25rem 0.7rem; border: 1px solid var(--border);
  border-radius: var(--radius-xs); color: var(--text-muted); }
.pagination a:hover { border-color: var(--accent); color: var(--text); text-decoration: none; }
.pagination [aria-current="page"] { background: var(--selected); color: var(--bg); border-color: var(--selected); }
.crumbs { font-size: var(--type-small); color: var(--text-muted); padding: var(--space-3) 0 0; }
.crumbs ol { list-style: none; display: flex; gap: 0.4rem; margin: 0; padding: 0; flex-wrap: wrap; }
.crumbs li:not(:last-child)::after { content: "/"; margin-left: 0.4rem; color: var(--border); }

/* Detaljsida */
.detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: var(--space-3);
  margin: var(--space-3) 0; }
.kv { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--space-2) var(--space-3); }
.kv dt { color: var(--text-muted); font-size: var(--type-micro); text-transform: uppercase; letter-spacing: 0.04em; }
.kv dd { margin: 0; font-weight: 600; }
.note { border-left: 3px solid var(--accent); background: var(--bg-raised); padding: var(--space-2) var(--space-3);
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0; color: var(--text-muted); font-size: var(--type-small); }
.prose { max-width: 46rem; }
.prose h2 { font-size: var(--type-body-l); margin-top: var(--space-4); }

/* "Case file"-behandling för klagomålsärendets sida (GET /complaints/:id,
   ADR-0058) — klassattribut ovanpå redan riktig markup (ADR-0047 gav
   sidan sin händelseförlopp-<ol>; huvud/status-blocket fanns sedan
   tidigare). Ren nedtoning: enbart de redan grafit/antracit-tonade,
   kontrastbevisade tokens ovan (--bg/--bg-raised/--bg-card/--border/
   --text/--text-muted) återanvänds här — inga --chroma-*-toner, ingen
   .card-dna, inga nya hex-värden i detta scope. --good/--warn/--bad och
   .badge.good/.warn/.bad förblir helt orörda (styles.test.ts). */
.case-file { background: var(--bg-raised); border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--space-4); color: var(--text); }
.case-header { background: var(--bg-card); border: 1px solid var(--border); border-bottom: 2px solid var(--border);
  border-radius: var(--radius-xs); padding: var(--space-3); margin-bottom: var(--space-3);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", monospace; }
.case-header .detail-grid { margin-top: var(--space-2); margin-bottom: 0; }
/* Tidslinje (ADR-0047/ADR-0057/ADR-0058) — the shared .what-changed list
   (components.ts's changeTimeline()) gets a vertical connecting-line
   treatment ONLY on the case-file page, scoped under .case-file so the
   plain rating-freshness usage elsewhere (casino pages) is untouched.
   Removes the list's default marker and draws the line with ::before
   (node) + ::after (segment down to the next entry), enbart i
   var(--border) — ingen ny färg. */
.case-file .what-changed { list-style: none; margin: var(--space-3) 0 0; padding: 0; }
.case-file .what-changed .change-line { position: relative; padding: 0 0 var(--space-3) 1.25rem; }
.case-file .what-changed .change-line:last-child { padding-bottom: 0; }
.case-file .what-changed .change-line::before { content: ""; position: absolute; left: 0; top: 0.3rem; width: 0.6rem; height: 0.6rem;
  border-radius: 50%; background: var(--bg-card); border: 2px solid var(--border); }
.case-file .what-changed .change-line::after { content: ""; position: absolute; left: 0.25rem; top: 0.95rem; bottom: -0.15rem; width: 1px;
  background: var(--border); }
.case-file .what-changed .change-line:last-child::after { content: none; }
.case-file .what-changed time { display: inline-block; color: var(--text-muted); font-variant-numeric: tabular-nums;
  margin-right: 0.4rem; }

/* ── Klagomål (P34): kategori-chip, statusrad, tidslinje, bevis ── */
.complaint-file .cf-badges { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center;
  margin: var(--space-2) 0; }
.cat { --cat: var(--text-muted); display: inline-flex; align-items: center; gap: 0.4rem;
  padding: var(--space-0) var(--space-2); background: var(--bg-card); color: var(--text);
  border: 1px solid var(--border); border-left: 4px solid var(--cat); border-radius: var(--radius-xs);
  font-size: var(--type-small); }
.cat-icon { display: inline-grid; place-items: center; min-width: 1.4rem; height: 1.4rem; padding: 0 0.2rem;
  border-radius: 4px; background: color-mix(in srgb, var(--cat) 24%, transparent); color: var(--text);
  font-size: var(--type-micro); font-weight: 700; line-height: 1; }
.cat[data-cat="Withdrawal"] { --cat: var(--chroma-coral); }
.cat[data-cat="Deposit"] { --cat: var(--chroma-orange); }
.cat[data-cat="Bonus"] { --cat: var(--chroma-yellow); }
.cat[data-cat="KYC"] { --cat: var(--chroma-ice); }
.cat[data-cat="AccountClosure"] { --cat: var(--chroma-pink); }
.cat[data-cat="WinningsConfiscation"] { --cat: var(--chroma-violet-hyper); }
.cat[data-cat="GameDispute"] { --cat: var(--chroma-cyan); }
.cat[data-cat="Payment"] { --cat: var(--chroma-mint); }
.cat[data-cat="Terms"] { --cat: var(--chroma-lime); }
.cat[data-cat="Support"] { --cat: var(--chroma-violet); }
.cat[data-cat="ResponsibleGambling"] { --cat: color-mix(in srgb, var(--chroma-mint) 55%, var(--chroma-cyan)); }
.cat[data-cat="Technical"] { --cat: color-mix(in srgb, var(--chroma-orange) 55%, var(--chroma-pink)); }
.cat[data-cat="Other"] { --cat: var(--text-muted); }
.cat.cat-none { border-left-color: var(--border); color: var(--text-muted); }

.complaint-file section + section { margin-top: var(--space-4); }
.complaint-file h2 { margin-top: 0; }
.complaint-file .cf-note { color: var(--text-muted); font-size: var(--type-small); max-width: 62ch; }

/* Tidslinje: vertikal stepper; details/summary ger dragspel på mobil utan extra JS-beroende. */
.complaint-timeline { list-style: none; margin: var(--space-3) 0 0; padding: 0; }
.complaint-timeline > li { position: relative; padding: 0 0 var(--space-3) 1.5rem; }
.complaint-timeline > li:last-child { padding-bottom: 0; }
.complaint-timeline > li::before { content: ""; position: absolute; left: 0; top: 0.35rem; width: 0.7rem; height: 0.7rem;
  border-radius: 50%; background: var(--bg-card); border: 2px solid var(--accent); }
.complaint-timeline > li::after { content: ""; position: absolute; left: 0.3rem; top: 1.1rem; bottom: -0.1rem; width: 1px;
  background: var(--border); }
.complaint-timeline > li:last-child::after { content: none; }
.complaint-timeline summary, .evidence-item summary { cursor: pointer; min-height: 44px; display: flex; flex-wrap: wrap;
  gap: var(--space-1) var(--space-2); align-items: center; padding: var(--space-1) 0; list-style: none; }
.complaint-timeline summary::-webkit-details-marker, .evidence-item summary::-webkit-details-marker { display: none; }
.complaint-timeline summary::after, .evidence-item summary::after { content: "\25BE"; color: var(--text-muted);
  margin-left: auto; transition: transform var(--motion-micro) var(--ease-out-spectral); }
.complaint-timeline details:not([open]) > summary::after, .evidence-item:not([open]) > summary::after {
  transform: rotate(-90deg); }
.complaint-timeline summary:focus-visible, .evidence-item summary:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--radius-xs); }
.tl-static { min-height: 44px; display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-2); align-items: center; }
.tl-title { font-weight: 600; }
.complaint-timeline time { color: var(--text-muted); font-variant-numeric: tabular-nums; font-size: var(--type-small); }
.tl-body { padding: 0 0 var(--space-1); color: var(--text-muted); font-size: var(--type-small); }
.tl-body p { margin: var(--space-1) 0; }

/* Bevis */
.evidence-list { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: grid; gap: var(--space-2); }
.evidence-item { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-xs);
  padding: 0 var(--space-3); }
.evidence-item[open] { padding-bottom: var(--space-3); }
.evidence-item .ev-hash { font-family: ui-monospace, SFMono-Regular, "SF Mono", Consolas, monospace;
  font-size: var(--type-small); color: var(--text-muted); }
.evidence-item dl { margin: var(--space-2) 0 0; display: grid; grid-template-columns: max-content 1fr; gap: var(--space-1) var(--space-3); }
.evidence-item dt { color: var(--text-muted); }
.evidence-item dd { margin: 0; }

/* Casinosidans sektion */
.casino-complaints .cc-list { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: grid; gap: var(--space-2); }
.casino-complaints .cc-list li { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center;
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-xs); padding: var(--space-2) var(--space-3); }
.casino-complaints .cc-list time { color: var(--text-muted); font-size: var(--type-small); margin-left: auto; }
.casino-complaints .chip-row .chip small { color: inherit; opacity: 0.85; }

@media (max-width: 639px) {
  .complaint-file { padding: var(--space-3); }
  .evidence-item dl { grid-template-columns: 1fr; }
  .evidence-item dt { margin-top: var(--space-1); }
  .casino-complaints .cc-list time { margin-left: 0; flex-basis: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .complaint-timeline summary::after, .evidence-item summary::after { transition: none; }
}

/* Footer */
.site-footer { border-top: 1px solid var(--border); margin-top: var(--space-5); padding: var(--space-4) 0;
  color: var(--text-muted); font-size: var(--type-small); }
.site-footer .wrap { display: flex; gap: var(--space-4); flex-wrap: wrap; justify-content: space-between; }
.site-footer nav { display: flex; gap: var(--space-3); flex-wrap: wrap; }
/* Footer 5-column structure (ADR-0075: Explore/Tools/CASIN0/Trust/Legal).
   Same small-heading treatment as .nav-eyebrow (uppercase, tracked, accent-
   colored) sized down to fit the footer's already-smaller 0.88rem base font,
   and its own .footer-cols nav rule (higher specificity than the generic
   .site-footer nav row above) switches each column's link list from that
   rule's horizontal chip-row layout to a vertical stack -- the density-toggle
   chip-row nav and the language-switcher div above are untouched. */
.footer-cols { display: grid; grid-template-columns: 1fr; gap: var(--space-4); flex-basis: 100%; }
.footer-col-heading { text-transform: uppercase; letter-spacing: 0.08em; font-size: var(--type-micro);
  color: var(--accent); margin: 0 0 var(--space-1); }
.footer-cols nav { flex-direction: column; flex-wrap: nowrap; gap: var(--space-1); align-items: flex-start; }
@media (min-width: 30rem) { .footer-cols { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 56rem) { .footer-cols { grid-template-columns: repeat(5, 1fr); } }
.empty { color: var(--text-muted); padding: var(--space-4); border: 1px dashed var(--border);
  border-radius: var(--radius); text-align: center; }
/* UX-23: det rika tomma tillståndet. Titeln bär vikt så att raden läses som
   ett svar och inte som en felrad, kroppen står i brödtextgrad, och åtgärden
   är en riktig kontroll med egen träffyta — inte en länk gömd i en mening.
   Graderna kommer ur typskalan; ett eget decimalvärde här fälls av
   src/web/__tests__/type-scale.test.ts. */
.empty-title { margin: 0 0 var(--space-1); color: var(--text); font-weight: 600;
  font-size: var(--type-body-l); }
.empty-body { margin: 0; max-width: 56ch; }
.empty-action { margin: var(--space-3) 0 0; }
@media (max-width: 40rem) { .header-search { display: none; } }

/* ── v2-komponenter: ratings, pros/cons, faq, filter, bonus, trust ── */
.stars { color: var(--rating); letter-spacing: 0.08em; }
.score-num { color: var(--text-muted); font-size: var(--type-small); }
.rating-row { display: flex; align-items: center; gap: var(--space-2); margin: 0.3rem 0; font-size: var(--type-small); }
.rating-dim { min-width: 7.5rem; color: var(--text-muted); }
.rating-bar { flex: 1; height: 0.45rem; background: var(--bg); border: 1px solid var(--border); border-radius: 999px; overflow: hidden; display: inline-block; }
.rating-fill { display: block; height: 100%; background: var(--accent); }
.rating-val { min-width: 2.2rem; text-align: right; font-variant-numeric: tabular-nums; }
/* Shared "what changed" treatment (ADR-0057): changeLine()/changeTimeline()
   (web/components.ts) render both rating-dimension freshness (ADR-0034,
   one item) and the complaint event timeline (ADR-0047, several) through
   this same list — replaces the old ad hoc .rating-freshness muted
   paragraph and the complaint page's unstyled bare ol. */
.what-changed { list-style: none; margin: 0.3rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.25rem; }
.change-line { color: var(--text-muted); font-size: var(--type-small); }
.change-line time { font-variant-numeric: tabular-nums; }
.pros-cons { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: var(--space-3); margin: var(--space-3) 0; }
.pros-cons .pros, .pros-cons .cons { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-3); }
.pros-cons h3 { margin-top: 0; font-size: var(--type-small); }
.pros ul, .cons ul { margin: 0; padding-left: 0; list-style: none; }
.pros li { color: var(--good); }
.cons li { color: var(--bad); }
.faq dt { font-weight: 600; margin-top: var(--space-2); }
.faq dd { margin: 0.2rem 0 0; color: var(--text-muted); }
.filter-sidebar { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-3); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-end; margin: var(--space-3) 0; }
.filter-sidebar .f-group { display: flex; flex-direction: column; gap: 0.2rem; min-width: 8rem; }
.filter-sidebar label { color: var(--text-muted); font-size: var(--type-micro); text-transform: uppercase; letter-spacing: 0.04em; }
.filter-sidebar input[type="text"], .filter-sidebar input[type="number"], .filter-sidebar select, .filter-sidebar input[type="search"], .filter-sidebar textarea { background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-xs); padding: 0.35rem 0.5rem; min-width: 7rem; }
.filter-sidebar textarea { font: inherit; min-width: 16rem; max-width: 100%; resize: vertical; }
.filter-sidebar .f-check { display: flex; align-items: center; gap: 0.3rem; color: var(--text-muted); font-size: var(--type-small); padding-bottom: 0.35rem; }
/* .filter-sidebar button's own default/hover/focus/active/disabled rules
   moved to the unified .btn system below (UX-50) — same alias treatment as
   .filterbar .apply just above; no call site changes. */
.filter-sidebar a.clear { color: var(--text-muted); font-size: var(--type-small); }
.catalog-filters input[type="search"], .catalog-filters input[type="text"], .catalog-filters input[type="number"], .catalog-filters select, .catalog-filters button { min-height: 44px; font-size: var(--type-body); max-width: 100%; }
.catalog-filters .f-group { flex: 1 1 9rem; min-width: 0; }
.catalog-filters .f-group:has(input[type="search"]) { flex-basis: 100%; }
.catalog-filters .f-check, .catalog-filters a.clear { min-height: 44px; display: inline-flex; align-items: center; }
.catalog-advanced { flex-basis: 100%; min-width: 0; border-block: 1px solid var(--border); }
.catalog-advanced summary { cursor: pointer; min-height: 44px; padding: 0.6rem 0; font-weight: 600; }
.catalog-advanced-fields { display: flex; flex-wrap: wrap; gap: var(--space-3); padding-bottom: var(--space-3); }
.catalog-advanced .muted { font-size: var(--type-small); }
/* UX-49: tillståndsstilar för katalogfälten — infört tillsammans med
   type="number" på de tio numeriska filtren (rtp/paylines/max_win/bet/
   reels). :invalid fångar webbläsarens egen constraint-validation (värdet
   bryter mot min/max/step, t.ex. rtp_min=999 mot max="100"); [aria-invalid]
   är samma tillstånd fast satt av servern självt (samma regex som WHERE-
   filtreringen), så stilen träffar oavsett om webbläsaren räknar likadant.
   Färgen är --critical (=--bad, se tokenkommentaren överst i filen — ingen
   ny hex, samma disciplin som resten av kit:et). */
.catalog-filters input:disabled, .catalog-filters select:disabled { opacity: 0.5; cursor: not-allowed; background: var(--bg-raised); }
.catalog-filters input:invalid, .catalog-filters input[aria-invalid="true"] { border-color: var(--critical); background: color-mix(in srgb, var(--critical) 10%, var(--bg)); }
.catalog-filters .f-hint { color: var(--critical); font-size: var(--type-small); margin: 0.2rem 0 0; }
/* DATA-06: a static explanatory note (e.g. the theme filter's "derived, not
   provider-declared" disclosure) — deliberately NOT .f-hint's --critical
   red, which src/web/__tests__/filter-input-validation.test.ts asserts is
   reserved for "the server rejected this submitted value" only. */
.catalog-filters .f-note { color: var(--text-muted); font-size: var(--type-small); margin: 0.2rem 0 0; }
.catalog-filters .f-note a { color: inherit; }

/* ── /casinos facet panel (ADR-0184) ─────────────────────────────────────── */
.facet-toolbar { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-end; flex: 1 0 100%; min-width: 0; }
.facet-note { color: var(--text-muted); font-size: var(--type-small); margin: var(--space-1) 0 0; flex: 1 0 100%; }
.facet-panel { flex: 1 0 100%; min-width: 0; border-top: 1px solid var(--border); }
.facet-panel > summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; gap: var(--space-2); font-weight: 600; list-style: none; }
.facet-groups { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-2); padding: var(--space-2) 0; }
.facet-group { flex: 1 1 13rem; min-width: 0; max-width: 100%; border: 1px solid var(--border); border-radius: var(--radius-xs); background: var(--bg); }
.facet-group > summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; gap: var(--space-2); padding: 0 var(--space-2); font-size: var(--type-small); color: var(--text); list-style: none; }
.facet-name { flex: 1 1 auto; min-width: 0; }
/* display:flex removes the browser's disclosure marker, so draw our own: a chevron that points
   down when closed and up when open. Border-based (no glyph, no image): it follows the text
   colour in every theme and in forced-colors mode. */
.facet-panel > summary::-webkit-details-marker, .facet-group > summary::-webkit-details-marker { display: none; }
.facet-panel > summary::after, .facet-group > summary::after { content: ""; flex: none; width: 0.5rem; height: 0.5rem; margin-inline-start: auto; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: translateY(-25%) rotate(45deg); }
.facet-group > summary::after { margin-inline-start: var(--space-1); }
.facet-panel[open] > summary::after, .facet-group[open] > summary::after { transform: translateY(25%) rotate(-135deg); }
.facet-group[open] > summary { border-bottom: 1px solid var(--border); }
.facet-badge { border: 1px solid var(--border); border-radius: 999px; padding: 0 var(--space-2); font-size: var(--type-micro); color: var(--text-muted); font-weight: 400; white-space: nowrap; }
.facet-body { padding: var(--space-2); }
.facet-options { border: 0; margin: 0; padding: 0; min-width: 0; max-height: 15rem; overflow-y: auto; display: flex; flex-direction: column; gap: var(--space-0); }
.filter-sidebar .facet-option { display: flex; align-items: center; gap: var(--space-2); min-height: 2.25rem; font-size: var(--type-small); color: var(--text); text-transform: none; letter-spacing: 0; cursor: pointer; }
.filter-sidebar .facet-option input { width: 1.1rem; height: 1.1rem; flex: none; accent-color: var(--accent); }
.facet-label { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.facet-n { flex: none; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.facet-body .facet-note { flex: none; }
.facet-unavailable { color: var(--text-muted); font-size: var(--type-small); margin: 0 0 var(--space-2); }
.chip[aria-current="page"] { background: var(--selected); color: var(--bg); border-color: var(--selected); }

/* Wide screens: show the panel content without a toggle. Guarded by @supports so a
   browser without ::details-content keeps the disclosure at every width. */
@media (min-width: 48rem) {
  @supports selector(details::details-content) {
    .facet-panel::details-content { content-visibility: visible; }
    .facet-panel > summary { display: none; }
  }
}

/* Compact view: the same casinoCard() markup, one dense row per casino. */
.grid-compact { grid-template-columns: 1fr; gap: var(--space-2); }
.grid-compact .casino-card { flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--space-1) var(--space-3); padding: var(--space-2) var(--space-3); }
.grid-compact .casino-card h2 { flex: 1 1 12rem; min-width: 0; }
.grid-compact .casino-card .meta { flex: 1 1 10rem; }
.grid-compact .casino-card .foot { margin-top: 0; flex: 2 1 16rem; }
@media print { .facet-panel, .facet-groups { display: none !important; } }


/* UX-50 — Unified button system (kanon §18.1's default/hover/focus-visible/
   pressed/disabled requirement, generalized past the single .cta class it
   was first built for). One source of truth: .btn/.btn--primary and the
   three pre-existing button styles (.cta, .filter-sidebar button,
   .filterbar .apply — previously three separate rules with three different
   paddings) are now the SAME rule, via a shared selector list, so they
   can't drift again. No call site changes — every existing class="cta" /
   class="filter-sidebar" button / class="filterbar" .apply still resolves,
   just off .btn--primary's values instead of its own copy. Built only on
   tokens this file already defines (--accent/--accent-strong/--bg, --border
   for .btn--secondary, --motion-micro + --ease-out-spectral confirmed
   present above for UX-41) — no new custom property invented here. */
.btn, .btn--primary,
.cta,
.filter-sidebar button,
.filterbar .apply {
  display: inline-block; background: var(--accent); color: var(--bg); font-weight: 700; padding: 0.55rem 1.2rem; border-radius: var(--radius-xs);
  border: none; font: inherit; cursor: pointer; min-height: 44px; box-sizing: border-box;
  transition: background-color var(--motion-micro) var(--ease-out-spectral),
    box-shadow var(--motion-micro) var(--ease-out-spectral),
    transform var(--motion-micro) var(--ease-out-spectral); }
.btn:hover, .btn--primary:hover,
.cta:hover,
.filter-sidebar button:hover,
.filterbar .apply:hover { background: var(--accent-strong); text-decoration: none; }
/* En egen halo, inte bara sidans generella outline (--focus är samma nyans
   som --accent, så en identisk outline hade knappt synts mot knappens egen
   bakgrund). */
.btn:focus-visible, .btn--primary:focus-visible,
.cta:focus-visible,
.filter-sidebar button:focus-visible,
.filterbar .apply:focus-visible {
  outline: 2px solid var(--bg); outline-offset: 2px;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 45%, transparent); }
.btn:active, .btn--primary:active,
.cta:active,
.filter-sidebar button:active,
.filterbar .apply:active { background: color-mix(in srgb, var(--accent-strong) 82%, black); transform: translateY(1px); }
.btn[disabled], .btn:disabled, .btn--primary[disabled], .btn--primary:disabled,
.cta[aria-disabled="true"], .cta:disabled,
.filter-sidebar button[disabled], .filter-sidebar button:disabled,
.filterbar .apply[disabled], .filterbar .apply:disabled { opacity: 0.5; pointer-events: none; cursor: not-allowed; }

/* Secondary: same shape/motion/states as primary, outlined instead of
   filled — --border is this file's existing neutral outline token, no new
   color invented. Not yet used by any call site in this PR (kanon §18.1
   only asked for the primary today); it exists so the next screen that
   needs a lower-emphasis action has one real class to reach for instead of
   a fourth one-off. */
.btn--secondary { display: inline-block; background: transparent; color: var(--text); font-weight: 700; padding: 0.55rem 1.2rem;
  border-radius: var(--radius-xs); border: 1.5px solid var(--border); font: inherit; cursor: pointer; min-height: 44px; box-sizing: border-box;
  transition: background-color var(--motion-micro) var(--ease-out-spectral),
    border-color var(--motion-micro) var(--ease-out-spectral),
    color var(--motion-micro) var(--ease-out-spectral),
    transform var(--motion-micro) var(--ease-out-spectral); }
.btn--secondary:hover { border-color: var(--accent); color: var(--accent); }
.btn--secondary:focus-visible { outline: 2px solid var(--bg); outline-offset: 2px;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 45%, transparent); }
/* UX-04: --border is now itself translucent (white 8%, kanon Appendix A.2)
   instead of the old opaque #2a2f4d. The 40% mix below predated that and
   was measured against the opaque value — mixed again at 40% against an
   already-8%-alpha color it compounds to ~3.2% white, visually gone (checked
   in a real render: indistinguishable from the button's own resting
   background). 100% keeps a single layer of translucency — i.e. this
   pressed state is just --border itself, the same token the hover/focus
   states already reuse, no new color invented. */
.btn--secondary:active { background: color-mix(in srgb, var(--border) 100%, transparent); color: var(--accent); transform: translateY(1px); }
.btn--secondary[disabled], .btn--secondary:disabled { opacity: 0.5; pointer-events: none; cursor: not-allowed; }

/* Icon-only: same fill/motion/states as primary, square at the same 44px
   min touch target instead of horizontal label padding. */
.btn--icon { display: inline-flex; align-items: center; justify-content: center; background: var(--accent); color: var(--bg);
  border: none; border-radius: var(--radius-xs); width: 44px; height: 44px; padding: 0; cursor: pointer; box-sizing: border-box;
  transition: background-color var(--motion-micro) var(--ease-out-spectral),
    box-shadow var(--motion-micro) var(--ease-out-spectral),
    transform var(--motion-micro) var(--ease-out-spectral); }
.btn--icon:hover { background: var(--accent-strong); }
.btn--icon:focus-visible { outline: 2px solid var(--bg); outline-offset: 2px;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 45%, transparent); }
.btn--icon:active { background: color-mix(in srgb, var(--accent-strong) 82%, black); transform: translateY(1px); }
.btn--icon[disabled], .btn--icon:disabled { opacity: 0.5; pointer-events: none; cursor: not-allowed; }
/* External CTA (kanon §18.3, §1.6: "External casino CTAs are visually
   distinct from internal CASIN0 actions"). Innan den här klassen fanns
   återanvände den sponsrade annonsens knapp OCH bonus->casino-länken exakt
   .cta ovan — en besökare kunde inte se på KNAPPEN att den lämnade sajten.
   Konturerad, inte fylld: samma komposition som .badge.sponsor/.ad-slot
   redan använder för "det här är kommersiellt", så språket är konsekvent
   snarare än en tredje, ny stil. --sponsor är plattformens redan
   etablerade kommersiella ton (skild från --accent) — ingen ny färg
   uppfanns. */
.cta-external { display: inline-flex; align-items: center; gap: 0.4em; background: transparent;
  color: var(--sponsor); border: 1.5px solid var(--sponsor); font-weight: 700;
  padding: calc(0.55rem - 1.5px) calc(1.2rem - 1.5px); border-radius: var(--radius-xs);
  min-height: 44px; box-sizing: border-box;
  transition: background-color var(--motion-micro) var(--ease-out-spectral), color var(--motion-micro) var(--ease-out-spectral), box-shadow var(--motion-micro) var(--ease-out-spectral); }
.cta-external:hover { background: var(--sponsor); color: var(--bg); text-decoration: none; }
.cta-external:focus-visible { outline: 2px solid var(--bg); outline-offset: 2px;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--sponsor) 45%, transparent); }
.cta-external:active { background: color-mix(in srgb, var(--sponsor) 82%, black); color: var(--bg); }
/* Pilen är ett riktigt ::after-element, inte ett löst unicode-tecken
   sammanfogat i mallsträngen — den ärver knappens egen färg/hover i
   stället för att vara ett dött tecken ingen stil når. */
.cta-external::after { content: "↗"; line-height: 1; }
/* ADR-0131 direct-play entry state. Every game has this surface; the badge
   states whether an authorized source is ready before the player is opened. */
.demo-frame { margin: var(--space-3) 0; display: flex; align-items: center;
  flex-wrap: wrap; gap: var(--space-2); }
.demo-frame .cta-muted { background: var(--bg-raised); color: var(--text); border: 1px solid var(--border); }
.demo-frame p { margin: 0.35rem 0 0; font-size: var(--type-small); }
.demo-frame p { flex-basis: 100%; }

/* Full-viewport, script-free player shell. The provider URL only reaches
   this iframe after the rights and exact-origin gates pass server-side. */
.game-player-page { min-height: 100vh; background: var(--bg); }
.game-player-shell { min-height: 100vh; display: grid; grid-template-rows: auto 1fr auto; }
.game-player-toolbar, .game-player-foot { display: flex; align-items: center; gap: var(--space-3);
  justify-content: space-between; padding: var(--space-2) var(--space-3); background: var(--bg-card);
  border-bottom: 1px solid var(--border); }
.game-player-foot { border-top: 1px solid var(--border); border-bottom: 0; color: var(--text-muted);
  font-size: var(--type-small); }
.game-player-foot p { margin: 0; }
.game-player-frame-wrap { min-height: 0; background: #000; }
.game-player-frame { display: block; width: 100%; height: 100%; min-height: 70vh; border: 0; }
.game-player-unavailable { align-self: center; justify-self: center; max-width: 42rem; margin: var(--space-4);
  padding: var(--space-5); text-align: center; background: var(--bg-card); border: 1px solid var(--border);
  border-radius: var(--radius); }
.game-player-unavailable h1 { margin-top: 0; }
.bonus-card .terms { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--space-2); margin-top: var(--space-2); }
.trust-18 { display: inline-flex; align-items: center; justify-content: center; width: 2.6rem; height: 2.6rem; border: 2px solid var(--text-muted); border-radius: 50%; font-weight: 800; color: var(--text-muted); flex: none; }
.review-head { display: flex; gap: var(--space-4); align-items: center; flex-wrap: wrap; margin: var(--space-3) 0; }
.verdict-note { border-left: 3px solid var(--warn); background: var(--bg-raised); padding: var(--space-2) var(--space-3); border-radius: 0 var(--radius-xs) var(--radius-xs) 0; color: var(--text-muted); font-size: var(--type-small); }

/* ── v4-komponenter: comparison, glossary, faq-accordion, artiklar, hero-v2 ── */
/* Startsidans egen hero-variant (kanon §2: Home = "maximum atmosphere,
   portal, large editorial type"). Var tidigare oanvänd död CSS OCH bar fel
   färger: rgba(88,166,255) och rgba(63,185,80) är exakt #58a6ff och #3fb950
   — GitHubs mörka tema, samma hexvärden det här filens egen toppkommentar
   (rad 22-23) säger togs bort överallt. Rättat till samma violett/cyan-par
   som .atmosphere redan använder (rad 865-866), och kopplad till startsidans
   faktiska hero (routes/landing.ts) — den enda .hero-sektionen som finns i
   produktion (grep bekräftar: class="hero" förekommer ingen annanstans).
   Kanons Display XL-steg (§6.2, "endast signaturhjältar") applicerades
   MEDVETET INTE här trots att den också är död CSS: startsidans h1 är en
   hel mening ("iGaming-kunskap med källkedja, granskning och mätbar
   bevisning"), inte en kort signaturfras, och en riktig skärmdump vid
   1280px visade texten svälla till fyra rader och klippas av skärmen. Att
   tvinga in registret på ett innehåll det inte är byggt för hade bytt en
   död token mot en trasig sida — se PR-beskrivningen för skärmdumpen som
   bevisar det. Display XL kvarstår oanvänd tills startsidan får en kort
   rubrikfras att bära den, ett redaktionellt beslut, inte ett CSS-beslut. */
.hero.hero-v2 { background:
  radial-gradient(60rem 18rem at 15% -40%, rgba(108, 57, 255, 0.14), transparent 60%),
  radial-gradient(45rem 14rem at 85% -50%, rgba(55, 216, 255, 0.08), transparent 60%); }
/* UX-47: the '.compare-table' base declaration below used to also be
   repeated verbatim further down (the games-compare table's own comment
   block, kanon §28.3/§19.4) — merged into this single copy so a future
   change to width/border-collapse/font-size can't land on only one of the
   two and quietly drift the games-compare table and every other
   '.compare-table' user (best-bonuses/best-casinos/AI-assistant/audit
   diff) apart from each other. */
.compare-table { width: 100%; border-collapse: collapse; font-size: var(--type-small); }
.compare-table th, .compare-table td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
.compare-table thead th { color: var(--text-muted); font-size: var(--type-micro); text-transform: uppercase; letter-spacing: 0.04em; }
.compare-table tbody tr:hover { background: var(--bg-card); }
.compare-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.rank { display: inline-flex; align-items: center; justify-content: center; min-width: 1.7rem; height: 1.7rem;
  border-radius: 50%; background: var(--bg-raised); border: 1px solid var(--border); font-size: var(--type-micro); font-weight: 700; color: var(--text-muted); }
.rank.top { color: var(--bg); background: var(--rank); border-color: var(--rank); }
/* UX-47: the pre-existing duplicate '.table-wrap' declaration that used to
   sit here (same selector as the one above, re-asserting only overflow-x)
   is now merged into that single declaration — two copies of the same
   selector made it easy for a future edit to land on the wrong one. */
/* Level 1 Prism Glass (kanon §8.2/§8.7) — see the material & depth token
   block below (§9's neighbour) for the BEFORE_STATE and rationale. */
.faq-accordion details { background: var(--depth-1-bg); border: 1px solid var(--depth-1-border); border-radius: var(--radius-xs); margin: var(--space-2) 0;
  -webkit-backdrop-filter: blur(var(--depth-1-blur)); backdrop-filter: blur(var(--depth-1-blur));
  box-shadow: inset 0 1px 0 var(--depth-1-sheen); }
.faq-accordion summary { cursor: pointer; padding: var(--space-2) var(--space-3); font-weight: 600; list-style: revert; }
.faq-accordion details[open] summary { border-bottom: 1px solid var(--border); }
.faq-accordion details > div { padding: var(--space-2) var(--space-3); color: var(--text-muted); }
.glossary-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr)); gap: var(--space-3); }
.glossary-item { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-3); }
.glossary-item dt { font-weight: 700; margin: 0; }
.glossary-item dd { margin: var(--space-1) 0 0; color: var(--text-muted); font-size: var(--type-small); }
.glossary-nav { display: flex; gap: var(--space-2); flex-wrap: wrap; margin: var(--space-3) 0; }
.glossary-nav a { display: inline-flex; align-items: center; justify-content: center; min-width: 2rem;
  padding: 0.15rem 0.5rem; border: 1px solid var(--border); border-radius: var(--radius-xs); font-size: var(--type-small); }
.article-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr)); gap: var(--space-3); }
.article-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); }
.article-card time { color: var(--text-muted); font-size: var(--type-micro); }
.article-card h3 { margin: 0; font-size: var(--type-body-l); }
.article-card p { margin: 0; color: var(--text-muted); font-size: var(--type-small); }
.kind-chip { display: inline-block; font-size: var(--type-micro); text-transform: uppercase; letter-spacing: 0.05em;
  padding: var(--space-0) var(--space-2); border-radius: 999px; border: 1px solid var(--border); color: var(--text-muted); }
.kind-chip.guide { color: var(--good); border-color: var(--good); }
.kind-chip.news { color: var(--accent); border-color: var(--accent); }
.kind-chip.review { color: var(--rating); border-color: var(--rating); }
.payments-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); gap: var(--space-3); }
.prose { max-width: 46rem; }
.prose h2 { font-size: var(--type-body-l); margin-top: var(--space-4); }
.prose h3 { font-size: var(--type-body); }
.notice { background: var(--bg-raised); border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0; padding: var(--space-2) var(--space-3); color: var(--text-muted); font-size: var(--type-small); }
.toc { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-3); font-size: var(--type-small); }
.toc ol { margin: 0; padding-left: 1.2rem; }

/* Safer-play pausbanderoll — injiceras av installPauseBanner() ovanför <main> på varje sida under paus. */
.pause-banner { background: var(--warn); color: #1a1200; }
.pause-banner .wrap { display: flex; align-items: center; justify-content: center; gap: var(--space-3);
  flex-wrap: wrap; padding: var(--space-2) var(--space-3); font-size: var(--type-small); text-align: center; }
.pause-banner a { color: #1a1200; font-weight: 700; text-decoration: underline; }
/* Verklighetskoll-banderoll (ADR-0067) — injiceras av installRealityCheckReminder() ovanför <main> när sessionstiden passerat tröskeln. Egen, lugnare ton än pausbanderollen: en påminnelse, inte en aktiv suppression av innehåll. */
.reality-check-banner { background: var(--bg-raised); border-bottom: 1px solid var(--border); color: var(--text); }
.reality-check-banner .wrap { display: flex; align-items: center; justify-content: center; gap: var(--space-3);
  flex-wrap: wrap; padding: var(--space-2) var(--space-3); font-size: var(--type-small); text-align: center; }
.reality-check-banner a { color: var(--accent); font-weight: 700; text-decoration: underline; }
@media print { .site-header, .site-footer, .filter-sidebar, .cta, .entity-sticky-group { display: none !important; } body { background: #fff; color: #000; } }

/* ── Chromatic Atlas våg 1 (ADR-0081, docs/design/CHROMATIC_ATLAS.md) ──
   Additiva tokens + atmosfär + signaler + intensitetslägen. Inga befintliga
   tokenVÄRDEN ändras (ADR-0022-alias-disciplinen); bas-repoint mot
   obsidian-900 är våg 2 med egen kontrastmätning (kanon §4.3). */
:root {
  /* Radius-skala (kanon Appendix A.1) — våg 1 introducerade denna skala med
     kommentaren "xs/sm aliasar befintliga värden": avsikten var att xs (6px)
     OCH sm (10px) bara skulle vara nya NAMN på två värden som redan fanns
     (gamla --radius-sm var 6px; --radius ovan är 10px) — ingen befintlig yta
     skulle ändra utseende. Mekaniken bröt det löftet: samma egenskapsnamn
     (--radius-sm) återanvändes för det nya 10px-värdet i detta senare
     :root-block, vilket enligt CSS-kaskaden tyst skuggade/skrev över den
     ursprungliga 6px-deklarationen högre upp i filen (nu borttagen som
     literal dead code av #168) för VARJE anropsställe som redan skrev
     var(--radius-sm) — cirka 16 komponenter designade mot 6px blev tyst
     10px utan att någon beslutade det. #168 städade bara syntaxen (tog bort
     den döda dubbeldeklarationen) utan att verifiera om 10px faktiskt var
     rätt för de befintliga anropsställena. Den här ändringen återställer
     ADR-0081:s egna "aliasar befintliga värden"-löfte: de ~16 ursprungliga
     anropsställena (skip-link, header-search, entity-sticky-bar,
     nav-menu-toggle, filterbar, pagination, note/verdict-note/notice,
     case-header, filter-sidebar, cta, faq-accordion, glossary-nav) pekar nu
     om till var(--radius-xs) (6px) och renderar exakt som före våg 1. sm
     (10px) är en genuint NY, additiv token (kanon Appendix A.1: "inputs/
     buttons") för wave-1-ytor byggda efter denna våg (t.ex. .filter-chip,
     .compare-token, .compare-cta) — de är oförändrade. */
  --radius-xs: 6px; --radius-sm: 10px; --radius-md: 14px; --radius-lg: 20px;
  --radius-xl: 28px; --radius-round: 999px;
  /* Motion-familjen (kanon §9.1 + Appendix A.4) — dekorativa animationer
     använder dessa; tillståndsändringar (fokus, disabled) är alltid omedelbara. */
  --motion-micro: 120ms; --motion-card: 180ms; --motion-overlay: 240ms;
  --motion-route: 320ms; --motion-hero: 550ms;
  --ease-out-spectral: cubic-bezier(0.22, 0.8, 0.24, 1);
  /* Layering (kanon Appendix A.5) — befintliga z-index-värden (40/50/60)
     aliasas; nya lager använder tokens, aldrig godtyckliga 999999. */
  --z-base: 0; --z-content: 10; --z-sticky: 40; --z-header: 50;
  --z-overlay: 60; --z-dialog: 70;
  --z-skip: 100; /* UX-40: sista lagret — skiplänken måste ligga över allt interaktivt. */
  /* UX-40: elevation (kanon Appendix A.3) — z-index-skuggor utan litteraler. */
  --elev-card: 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.25);
  --elev-panel: 0 4px 16px rgba(0, 0, 0, 0.45);
  --elev-overlay: 0 8px 32px rgba(0, 0, 0, 0.55);
  /* Signal-färggrammatik (kanon §15.4) — mappad på --chroma-*-primitiver.
     Färgen är FÖRSTÄRKNING; textbäraren är alltid själva etiketten. */
  --signal-new: var(--chroma-pink); --signal-updated: var(--chroma-cyan);
  --signal-verified: var(--chroma-mint); --signal-changed: var(--chroma-yellow);
  --signal-warning: var(--chroma-coral); --signal-action: var(--warn);
}
/* Atmosfär (kanon §5) — ljusfältsopaciteter som data-intensity skiftar.
   Bärare = eget dekorativt element direkt efter skip-länken (layout.ts):
   <body>/<html> får inga extra attribut — densitets-/familihjulstesterna
   pinnar exakta tagg-prefix (kanonens ärlighet: aldrig en attr-störning). */
:root {
  --atmo-violet: 0.05; --atmo-cyan: 0.03; --atmo-noise: 0.02;
}
.atmosphere {
  position: fixed; inset: 0; z-index: -2; pointer-events: none;
  background:
    radial-gradient(1100px 700px at 18% -10%, rgba(108, 57, 255, var(--atmo-violet)), transparent 70%),
    radial-gradient(900px 600px at 85% 0%, rgba(55, 216, 255, var(--atmo-cyan)), transparent 70%);
}
.atmosphere::after {
  content: ""; position: absolute; inset: 0;
  opacity: var(--atmo-noise);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}
.atmosphere[data-intensity="vivid"] {
  --atmo-violet: 0.08; --atmo-cyan: 0.06;
}
.atmosphere[data-intensity="calm"] {
  --atmo-violet: 0.015; --atmo-cyan: 0.01; --atmo-noise: 0.012;
}
/* Trust-mode (ADR-0084, kanon §23.1/§33): metodik-/transparensytorna tonar
   mot graphite — dekorativ mättnad minskar, ambiant shift aldrig en ny
   produkt. Kombineras med intensitetsvalet (min av de två). */
.atmosphere[data-mode="trust"] {
  --atmo-violet: 0.02; --atmo-cyan: 0.02; --atmo-noise: 0.015;
}
.atmosphere[data-mode="trust"][data-intensity="vivid"] {
  --atmo-violet: 0.035; --atmo-cyan: 0.03;
}

/* Chromatic Atlas material & depth system (kanon §8, docs/design/
   CHROMATIC_ATLAS.md) — BEFORE_STATE, re-verified fresh in this worktree:
   grep -c backdrop-filter src/web/styles.ts = 0, and no depth/material
   tokens existed anywhere in this file. Every panel-like surface (.card,
   admin .panel, .faq-accordion, .nav-overlay, .ad-slot) shared one flat,
   unlayered treatment — zero material system, despite kanon §8 describing
   six named materials and five depth levels.

   This introduces the tokens for exactly two of those tiers, narrowly:
   depth-1 = kanon §8.7 Level 1 ("standard panel") rendered in the §8.2
   Prism Glass composition (dark translucent core, 1px low-opacity border,
   slight inner top reflection, subtle backdrop blur) — for compact
   contextual surfaces that sit over page content (FAQ accordion items,
   admin .panel). depth-3 = kanon §8.7 Level 3 ("drawers/popovers/mega
   navigation") in the same Prism Glass language at a more opaque/blurred
   setting befitting a surface that fully occludes the page (the nav
   overlay). Levels 0/2/4 and the other five materials (Obsidian/
   Holographic Film/Chrome/Soft Plastic/Signal Mesh) are deliberately NOT
   built here. .card itself is untouched — every card variant (including
   this session's casino-card and bonus-card work) inherits it unchanged. */
:root {
/* UX-37/38 (kanon §8): namngivna materialklasser. .m-prism är kanon §8.2:s
   Prism Glass (mörk translucent kärna, låg-opacitetskant, inre toppreflex,
   blur endast bakom @supports — aldrig som krav). .m-holo/.m-plastic/.m-mesh
   är §8.3-8.6. Klasserna är additiva; ytor som redan bär depth-tokens
   (--depth-1/-3) behåller dem — .m-prism använder samma depth-3-värden. */
.m-prism {
  background: var(--depth-3-bg);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
@supports (backdrop-filter: blur(1px)) {
  .m-prism { -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); }
}
/* Riktad gradient över entity-DNA, ENDAST på valt tillstånd (aldrig yttapet). */
.m-holo {
  background-image: linear-gradient(135deg,
    color-mix(in srgb, var(--dna-primary, var(--accent)) 16%, transparent),
    color-mix(in srgb, var(--dna-secondary, var(--accent)) 16%, transparent));
}
/* Mjuk inre toppljusning + 1px mörk bottenkant — taktil chip-behandling. */
.m-plastic {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 1px 0 rgba(0, 0, 0, 0.4);
}
/* Tunn linje-mesh för relations-/trendytor — ALDRIG bakom brödtext (kanon §8.6). */
.m-mesh {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M0 24L24 0M-6 6L6 -6M18 30L30 18' stroke='rgba(255,255,255,0.04)' stroke-width='1' fill='none'/%3E%3C/svg%3E");
  background-size: 24px 24px;
}

  --depth-1-bg: rgba(19, 21, 42, 0.72); --depth-1-border: rgba(42, 47, 77, 0.65);
  --depth-1-sheen: rgba(255, 255, 255, 0.04); --depth-1-blur: 14px;
  --depth-3-bg: rgba(7, 8, 15, 0.82); --depth-3-border: rgba(42, 47, 77, 0.7);
  --depth-3-sheen: rgba(255, 255, 255, 0.05); --depth-3-blur: 22px;
}

/* Portal-hover (ADR-0084, kanon §3.1): hover/focus ökar spektral luminans,
   roterar ringen smått (~15deg) och vidgar "aperturen" ~1-2px — snabbt
   (micro-familjen), ingen layoutförändring, reduced-motion = statisk. */
.logo-portal .logo-portal-ring {
  transform-origin: 10px 10px;
  transition: transform var(--motion-micro) var(--ease-out-spectral),
    opacity var(--motion-micro) var(--ease-out-spectral);
}
a.logo:hover .logo-portal .logo-portal-ring,
a.logo:focus-visible .logo-portal .logo-portal-ring {
  transform: rotate(15deg) scale(1.06);
  opacity: 1;
}
.logo-portal .logo-portal-ring { opacity: 0.85; }
@media (prefers-reduced-motion: reduce) {
  a.logo:hover .logo-portal .logo-portal-ring,
  a.logo:focus-visible .logo-portal .logo-portal-ring {
    transform: none;
  }
}

/* Applied filter capsules (ADR-0084, kanon §18.14/§28.2) — facett + värde
   + ta-bort, alltid över resultatet; aldrig bara "3 filter". */
.applied-filters {
  display: flex; align-items: center; gap: var(--space-2);
  flex-wrap: wrap; margin: 0 0 var(--space-3);
}
.filter-chip {
  display: inline-flex; align-items: center; gap: 0.4em;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--bg-card) 70%, transparent);
  color: var(--text); font-size: var(--type-small);
  padding: var(--space-0) 0.6rem; text-decoration: none;
}
.filter-chip:hover { border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.filter-chip-label { color: var(--text-muted); }
.filter-chip-x { color: var(--text-muted); font-weight: 700; }
.filter-chip-clear { color: var(--text-muted); font-size: var(--type-small); text-decoration: underline; }
.nav-live-changed { margin: 0 0 var(--space-2); font-size: var(--type-small); color: var(--signal-changed); }
.nav-live-changed strong { font-variant-numeric: tabular-nums; margin-right: 0.3em; }

/* Compare Tray (ADR-0082, kanon §18.42)
/* Standard-balanced-värden på html-elementet (layout.ts sätter attributet). */

/* Atmosfärstack (kanon §5.1): obsidian-botten (var(--bg), oförändrad) +
   två radiala ljusfält + statisk grain — dekorativa lager UNDER innehållet,
   pointer-events none, aldrig ovanför text. Inga animerade lager. */

/* Signals (kanon §15.4/§18.16) — färg + text + punkt, aldrig färg ensam.
   Uppercase 11-12px mikrolabel enligt kanon §6.3 (kort signal-etikett). */
.signal {
  display: inline-flex; align-items: center; gap: 0.35em;
  font-size: var(--type-micro); font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; line-height: 1.4;
  padding: 0.15em 0.6em; border-radius: var(--radius-xs);
  border: 1px solid color-mix(in srgb, var(--signal-fg) 45%, transparent);
  color: var(--signal-fg); background: color-mix(in srgb, var(--signal-fg) 10%, transparent);
}
.signal::before { content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--signal-fg); flex: none; }
.signal--new { --signal-fg: var(--signal-new); }
.signal--updated { --signal-fg: var(--signal-updated); }
.signal--verified { --signal-fg: var(--signal-verified, var(--chroma-mint)); }
.signal--changed { --signal-fg: var(--signal-changed); }
.signal--warning { --signal-fg: var(--signal-warning); }
.signal--action { --signal-fg: var(--signal-action); }
/* Livslopp (kanon §25): NEW avmattning med ålder — styrkas via attributet,
   aldrig genom att byta signal-typ. */
.signal--new[data-age="fresh"] { box-shadow: 0 0 12px color-mix(in srgb, var(--signal-new) 35%, transparent); }
.signal--new[data-age="recent"] { box-shadow: none; }
/* a11y (live-prod-fynd 2026-09-29): opacity 0.75 sänkte kontrasten till
   3.22:1 (axe color-contrast, serious) på riktiga spel — fixtures hade bara
   färska created_at och renderade aldrig subtle. Lugn nu via ingen glöd;
   full opacitet behåller #ff2f9d ≥ 4.5:1 mot samtliga mörka ytor. */
.signal--new[data-age="subtle"] { box-shadow: none; }

/* Mobil bottom-nav (kanon §11.2/§18.26) — endast riktiga rutter, stabil
   ordning, search centralt. Dold på ≥768px där primär-nav redan syns. */
.bottom-nav {
  position: fixed; inset: auto 0 0 0; z-index: var(--z-header);
  display: flex; justify-content: space-around;
  background: color-mix(in srgb, var(--bg-raised) 92%, transparent);
  border-top: 1px solid var(--border);
  padding: 0.35rem 0 max(0.35rem, env(safe-area-inset-bottom));
}
.bottom-nav a {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-width: 44px; min-height: 44px; justify-content: center;
  color: var(--text-muted); font-size: var(--type-micro); letter-spacing: 0.04em;
  text-transform: uppercase; text-decoration: none;
}
.bottom-nav a[aria-current="page"] { color: var(--accent); }
.bottom-nav .bn-icon { width: 20px; height: 20px; }
@media (min-width: 768px) { .bottom-nav { display: none; } }
@media (max-width: 767px) { body { padding-bottom: 64px; } }

/* RTP-ändringshistorikens CHANGED-signalhuvud (ADR-0082): signalen sitter
   på sektionens rubrikrad — färg + text, aldrig färg ensam (§26.4). */
.rtp-changes-head { display: flex; align-items: center; gap: var(--space-2); }
.rtp-changes-heading { margin: 0; font-size: var(--type-body-l); }

/* Provable fair-sektionens VERIFIED-signalhuvud (ADR-0102) — samma
   flex-huvud-mönster som RTP-ändringshistorikens ovan: färg + text, aldrig
   färg ensam (§26.4). */
.provable-fair-head { display: flex; align-items: center; gap: var(--space-2); }
.provable-fair-heading { margin: 0; font-size: var(--type-body-l); }

/* Mega-menyvyns live-rail (ADR-0082, kanon §11.3/§18.23 zon 3) — den
   dynamiska högerspalten. Statiska zonerna (core/browse) rör aldrig; railen
   är ett självslutet block som onSend-injiceras sist i .nav-overlay-inner. */
.nav-live-rail {
  border-top: 1px solid var(--border);
  margin-top: var(--space-3);
  padding-top: var(--space-2);
}
.nav-live-count { margin: 0 0 var(--space-2); font-size: var(--type-small); }
.nav-live-count strong {
  font-size: var(--type-h3); font-variant-numeric: tabular-nums;
  color: var(--signal-new); margin-right: 0.35em;
}
.nav-live-list { list-style: none; margin: 0 0 var(--space-2); padding: 0; }
.nav-live-list li { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-2); padding: 0.15rem 0; font-size: var(--type-small); }
.nav-live-list a { color: var(--text); }
.nav-live-when { color: var(--text-muted); font-size: var(--type-micro); font-variant-numeric: tabular-nums; }
/* UX-56: the changed-casinos delta rows — each row stacks a casino name
   above its own changeTimeline()/changeLine() (.what-changed/.change-line,
   ADR-0057) delta line via ordinary block flow (the name <a> is inline,
   the .nav-live-delta <div> after it is block-level, so it wraps onto its
   own line with no flex layout needed) — this can't reuse .nav-live-list
   above: that rule is a flex space-between row built for a single
   name+age pair, not a name with a multi-line child under it. */
.nav-live-deltas { list-style: none; margin: 0 0 var(--space-2); padding: 0; }
.nav-live-deltas li { font-size: var(--type-small); padding: 0.15rem 0; }
.nav-live-deltas a { color: var(--text); }
.nav-live-delta { margin-top: 0.15rem; }
.nav-live-delta .what-changed { margin: 0; }

/* Games-compare tabell (ADR-0083, kanon §28.3/§19.4) — analyskammaren:
   strikt grid, källkedja per cell, ALDRIG en färgad vinnare.
   UX-47: the base '.compare-table { width/border-collapse/font-size }'
   declaration used to be repeated here verbatim (see the hero-v2 section
   above, where the one surviving copy now lives) — merged into that one,
   same reasoning as the '.table-wrap' merge above. */
.compare-table th[scope='row'] { text-align: left; color: var(--text-muted);
  font-weight: 600; padding: var(--space-2) var(--space-2) var(--space-2) 0; }
.compare-table td, .compare-table th { border-bottom: 1px solid var(--border);
  padding: var(--space-2); vertical-align: top; }
.compare-provider { display: block; color: var(--text-muted); font-size: var(--type-micro); }
/* UI-01: samma "en länk ska SE UT som en länk"-princip som a.meta-link ovan
   (rad 517) — leverantörsraden i jämförelsetabellens <th> blir en riktig
   länk när games-compare.ts har bevisat organisationen (provider_org_id +
   provider_name), och en klickbar yta som ser ut som den döda texten den
   ersatte är en yta ingen hittar. */
/* WCAG 2.5.8 target-size (found when /compare/games first joined
   scripts/a11y-check.mjs's PAGES, UI-01): axe flagged both this link and
   the header's entity-name link just above it (rule right below) because
   an inline anchor's box is only as tall as the inherited body
   line-height (1.6 × --type-micro/--type-small ≈ 21-22px) — one <th>
   padding short of the 24px minimum, and two links stacked in the same
   cell sit closer together than that once this row stopped being dead
   text. line-height alone — never font-size, which the typskalan-test
   above locks to the scale — grows the click/tap box without changing
   how the text reads. */
a.compare-provider { line-height: 24px; text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--text-muted) 55%, transparent);
  text-underline-offset: 0.2em; text-decoration-thickness: 1px; }
a.compare-provider:hover, a.compare-provider:focus-visible { color: var(--text);
  text-decoration-color: var(--text); }
/* Same fix, same cause, for the header's OTHER link (game/casino/bonus
   name) — shared by every .compare-table, not just the one that
   surfaced it. */
.compare-table th > a:first-child { display: inline-block; line-height: 24px; }
.compare-source { color: var(--text-muted); font-size: var(--type-micro); }

/* Compare Tray (ADR-0082, kanon §18.42) — fast botten-nivå, 72-84px desktop
   (kanon), prismmaterial, tokens + count + CTA + töm. Överlever navigation
   via cookie; injiceras post-render, dold i print.

   UX-26 — position: sticky, deliberately NOT position: fixed. The canon's
   own rule for this component is "Must not obscure mobile content or lose
   selections on navigation" (docs/design/CHROMATIC_ATLAS.md:1640); the
   second half held and the first did not. A fixed tray is out of flow, so
   it reserved nothing and painted straight over the end of the page.
   Measured before this change (real Chromium, no JS, 390x844,
   /games scrolled to the document end, one casino + one game selected):
   tray box top 724 / bottom 780 while .site-footer ended at 779.7 — the
   footer's last row (the Integritet / Villkor links, boxes 706.6-729.2 and
   733.2-755.7) sat ENTIRELY behind the tray, 6 content boxes intersected
   it, and body compensated with exactly the 64px the fixed .bottom-nav
   needs (:761) and not one pixel for the tray. At 1280x800 the same page
   hid 8 boxes behind a 72px tray with body padding-bottom 0px, so the
   defect was never mobile-only.

   Why sticky and not a tray-conditional padding on <body>: the tray's
   height is a min-height PLUS its content, so it grows. Measured on the
   same viewport with both baskets full and the "basket is full" notice row
   present (that row turns on flex-wrap — see .compare-tray-has-notice
   below): 243.5px, and 273px with the slightly longer slugs
   scripts/a11y-check.mjs seeds — not 56px. Any hardcoded compensation is
   short by 190px or more in precisely the case where the tray covers the
   most content and the visitor most needs to reach it. A sticky box stays
   in normal flow, so it reserves its OWN height — whatever that height is
   on this render — at the end of <body>, and is still pinned to the bottom
   of the viewport while the visitor scrolls. Because the reservation IS the
   element, an empty selection reserves nothing: no state-dependent class on
   <body>, and the pages that render no tray keep a dead strip of zero.

   Load-bearing invariant: the tray must remain the last IN-FLOW child of
   <body>. web/compare.ts:installCompareTray injects it immediately before
   </body>; .bottom-nav (emitted just above it, web/layout.ts:500) is
   position: fixed and takes no part in flow, so it does not disturb that.
   (WP-0.06 removed the fixed .save-tray that used to be injected just below
   the tray; the Saved indicator is now an icon button in the header.) Wrapping the tray in a fixed
   or absolutely positioned container would silently bring UX-26 back —
   scripts/a11y-check.mjs asserts the geometry in a real browser rather than
   trusting this comment.

   bottom is now a sticky offset, not half an inset pair: 0 on desktop and
   64px on mobile (see the media query below), i.e. exactly the strip body
   already reserves for .bottom-nav. Because the offset equals that
   reservation, the pinned position and the flow position coincide at
   maximum scroll and the tray does not jump at the end of the page.
   left/right go away with the inset shorthand: an in-flow block-level box
   already spans the full width of <body> (margin 0, no horizontal
   padding, :87).

   The one behavioural difference from fixed, stated plainly: when the WHOLE
   document fits inside the viewport there is nothing to stick to, so the
   tray renders at its flow position — directly under the footer — instead
   of hugging the window bottom. Measured: /about at 1280x2000 puts the tray
   at 1403.6-1475.6 with ~524px of empty page below it. Nothing is covered
   and every control stays visible and reachable; it is a cosmetic
   difference on unusually tall windows. It cannot happen at mobile widths
   in this layout at all — the footer alone measures 1542px tall at 390px
   — and the alternative (turning <body> into a flex column so the tray can
   be pushed down) would change margin collapsing on exactly the pages that
   carry a selection, i.e. make a page shift when a visitor picks a casino.
   Not worth it for empty space below a fully visible bar. */
.compare-tray {
  position: sticky; bottom: 0; z-index: var(--z-sticky);
  display: flex; align-items: center; gap: var(--space-2);
  min-height: 72px; padding: var(--space-2) var(--space-3);
  background: color-mix(in srgb, var(--bg-raised) 94%, transparent);
  border-top: 1px solid var(--border);
  overflow-x: auto;
}
.compare-tray-count {
  min-width: 28px; height: 28px; border-radius: var(--radius-round);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--chroma-violet); color: #fff; font-weight: 700;
  font-variant-numeric: tabular-nums; flex: none;
}
.compare-token {
  flex: none; color: var(--text); font-size: var(--type-small);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: var(--space-0) 0.6rem;
}
/* Hover/focus-visible for the tray's own controls (same visual language as
   .chip:hover/.pagination a:hover elsewhere in this file: border brightens
   to --accent, no underline). The global :focus-visible rule above already
   gives every link an outline; this adds the matching color/border state. */
.compare-token:hover, .compare-token:focus-visible {
  border-color: var(--accent); color: var(--text); text-decoration: none;
}
.compare-cta {
  flex: none; margin-left: auto; color: #fff; font-weight: 700;
  background: var(--chroma-violet); border-radius: var(--radius-sm);
  padding: 0.45rem 0.9rem; text-decoration: none;
}
.compare-clear { flex: none; color: var(--text-muted); font-size: var(--type-small); }
.compare-clear:hover, .compare-clear:focus-visible {
  color: var(--text); text-decoration: none;
}
/* Per-post-borttagning i trayen. /compare/remove och /compare/games/remove
   var registrerade men helt olänkade (web/compare.ts), så enda vägen ut var
   töm-länken ovan — som tömmer BÅDA korgarna. Varje token är nu ett par:
   entitetslänken plus en borttagningslänk, sammanfogade till ETT visuellt
   chip (token får raka högerhörn, borttagningen raka vänster + delad kant)
   så de läses som en enhet och inte som två lösa länkar. Samma ×-idiom som
   .filter-chip-x ovan, inte en ny komponenttyp. */
.compare-token-group { display: inline-flex; align-items: center; flex: none; }
.compare-token-group > .compare-token {
  border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: 0;
}
.compare-token-remove {
  flex: none; color: var(--text-muted); font-weight: 700; line-height: 1;
  border: 1px solid var(--border);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: var(--space-0) 0.5rem; text-decoration: none;
}
.compare-token-remove:hover, .compare-token-remove:focus-visible {
  border-color: var(--accent); color: var(--text); text-decoration: none;
}
/* Avvisat tillägg (korgen full): återanvänder .notice ovan och får en egen
   rad ÖVERST i trayen. Radbrytningen slås på av modifieraren och bara av
   den, så trayens vanliga enradslayout med overflow-x är orörd i det
   normala fallet. */
.compare-tray.compare-tray-has-notice { flex-wrap: wrap; }
.compare-tray-notice { flex: 1 0 100%; order: -1; margin: 0; }
@media print { .compare-tray { display: none !important; } }
/* Mobile: the sticky offset clears the fixed .bottom-nav strip — measured
   56.2px tall in a real Chromium at 390x844, and body reserves 64px for it
   at :761 — so the tray parks on top of that reservation instead of on top
   of the page's last content. min-height drops to 56px because the tray's
   own controls are tighter here; the compensation for the tray itself is the
   sticky box's own flow height, never this number (see .compare-tray). */
@media (max-width: 767px) { .compare-tray { bottom: 64px; min-height: 56px; } }

/* ── Jämför/Spara som växlingsknapp (selection-controls.ts) ──
   Läget bärs av aria-pressed (semantiken) OCH av text + bockmarkering — aldrig
   av färg ensam (WCAG 1.4.1). Bocken är dekor: den andra deklarationen ger den
   tom alternativtext där webbläsaren stödjer syntaxen, den första är reserven. */
a[data-selection][aria-pressed="true"] { font-weight: 700; }
a[data-selection][aria-pressed="true"]::before { content: "\2713\00a0"; content: "\2713\00a0" / ""; }
.chip[data-selection][aria-pressed="true"] {
  background: var(--selected); color: var(--bg); border-color: var(--selected);
}
.chip[data-selection][aria-pressed="true"]:hover { color: var(--bg); }
/* Ankaret efter en åtgärd: det klickade kortet landar med luft ovanför sig
   (och under den sticky headern) i stället för klistrat mot kanten. */
[id^="sel-"] { scroll-margin-top: 30vh; }

/* ── Sparat i headern (ersätter den fasta .save-tray-pillern) ──
   Sparat-ikonen och menyknappen sitter i EN flex-enhet (.header-actions, layout.ts)
   som delar högermarginalen: de radbryts aldrig isär, och enheten ligger kvar
   till höger även när den hamnar på en andra rad (som menyknappen redan gjorde
   vid ~768 px). */
.header-actions { margin-left: auto; display: flex; align-items: center; gap: var(--space-1); }
.header-actions .nav-menu { margin-left: 0; }
.header-saved {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
  border-radius: var(--radius-xs); color: var(--text); text-decoration: none;
}
.header-saved:hover, .header-saved:focus-visible { background: var(--bg-card); text-decoration: none; }
.header-saved--has .header-saved-icon path { fill: currentColor; }
.header-saved-count {
  position: absolute; top: 2px; right: 0; min-width: 18px; height: 18px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-round); background: var(--chroma-violet); color: #fff;
  font-size: var(--type-micro); font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums;
}
@media print { .header-saved { display: none !important; } }
/* Ikonen tar 48 px i bredd; på surfplattebredder där raden redan var knapp
   (menyknappen radbröts vid ~768 px) får sökfältet ge motsvarande utrymme, så
   att ikonen inte flyttar radbrytningen till ännu bredare skärmar. */
@media (min-width: 40.0625rem) and (max-width: 58rem) {
  .header-search input { min-width: 0; width: 9.5rem; }
}

/* ── Omnibox (PROMPT 18) ───────────────────────────────────────────────────
   Progressive enhancement: with no JavaScript this is an ordinary GET form
   (label + input + submit). web/omnibox-client.ts adds the combobox role,
   the suggestions panel, keyboard navigation and — on a phone — the
   full-screen surface (.is-active). Nothing below relies on JS to be
   readable. */
.omnibox .omni-field { position: relative; }
.omnibox .omni-row { display: flex; gap: var(--space-2); align-items: stretch; }
.omnibox .omni-input { flex: 1 1 auto; min-width: 0; }
.omnibox input.omni-input[type="search"] { min-height: 3rem; font-size: var(--type-body); padding: 0.6rem 0.9rem; }
.omni-hint { margin: var(--space-1) 0 0; font-size: var(--type-small); color: var(--text-muted); }
.omnibox .omni-row .omni-close { display: none; align-items: center; justify-content: center; min-width: 3rem; min-height: 3rem;
  background: var(--bg-card); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--radius-xs); font-size: var(--type-body); cursor: pointer; }
.omni-panel { position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-overlay); margin-top: var(--space-1);
  background: var(--bg-raised); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5); max-height: min(70vh, 32rem); overflow-y: auto;
  padding: var(--space-2); text-align: left; }
.omni-panel[hidden] { display: none; }
.omni-group-label { padding: var(--space-2) var(--space-2) var(--space-1); font-size: var(--type-micro);
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); }
.omni-opt { display: block; padding: var(--space-2) var(--space-3); border-radius: var(--radius-xs);
  color: var(--text); cursor: pointer; overflow-wrap: anywhere; }
.omni-opt:hover, .omni-opt[aria-selected="true"] { background: var(--bg-card); outline: 2px solid var(--accent); outline-offset: -2px; }
.omni-none { margin: 0; padding: var(--space-2) var(--space-3); color: var(--text-muted); }

/* Header omnibox: the small field grows while it is being used. */
.header-search { position: relative; }
.header-search.is-active input { min-width: min(26rem, 46vw); }
.header-search .omni-panel { left: auto; right: 0; width: min(30rem, 92vw); }

/* Phone: the search page's omnibox becomes a full-screen surface while active. */
@media (max-width: 40rem) {
  .omnibox.is-active { position: fixed; inset: 0; z-index: var(--z-dialog); margin: 0; border: 0; border-radius: 0;
    background: var(--bg); overflow-y: auto; align-content: flex-start; padding: var(--space-3); }
  .omnibox.is-active .omni-field { flex-basis: 100%; }
  .omnibox.is-active .omni-row .omni-close { display: inline-flex; }
  .omnibox.is-active .omni-panel { position: static; box-shadow: none; max-height: none; border: 0;
    background: transparent; padding: var(--space-2) 0; margin: 0; }
  html.omni-lock { overflow: hidden; }
}

/* Interpreted filters + typo correction + result tabs + saved searches */
.search-interp, .search-didyoumean, .search-saved, .search-save-form {
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--space-3); margin: var(--space-3) 0; }
.search-didyoumean { border-left: 3px solid var(--accent); }
.search-didyoumean p, .search-interp p { margin: var(--space-1) 0 0; }
.search-interp h2, .search-saved h2 { font-size: var(--type-body); margin: 0 0 var(--space-2); }
.omni-token { display: inline-flex; align-items: center; gap: var(--space-2); padding: var(--space-0) 0.7rem;
  border-radius: 999px; font-size: var(--type-small); background: var(--bg); border: 1px solid var(--border); color: var(--text); }
.omni-token a { display: inline-flex; align-items: center; justify-content: center; min-width: 24px; min-height: 24px;
  color: var(--text); font-weight: 700; text-decoration: none; }
.omni-token a:hover { color: var(--accent); }
.search-tabs .chip[aria-current="page"] { background: var(--selected); color: var(--bg); border-color: var(--selected); }
.search-saved ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.search-saved li { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-3); }
.search-saved .saved-meta { color: var(--text-muted); font-size: var(--type-small); }
.search-save-form { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); align-items: flex-end; }
.search-save-form label { color: var(--text-muted); font-size: var(--type-micro); text-transform: uppercase; letter-spacing: 0.04em; }
.search-save-form .f-group { display: flex; flex-direction: column; gap: 0.2rem; }
.search-save-form input[type="text"] { background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--radius-xs); padding: 0.35rem 0.5rem; min-height: 44px; }
.search-save-form .f-hint { flex-basis: 100%; margin: 0; color: var(--text-muted); font-size: var(--type-small); }

/* ── Personligt hem (member-home.ts) ── */
.mh-head p { max-width: 68ch; }
.mh-fair { color: var(--text-muted); font-size: var(--type-small); }
.mh-notice { border: 1px solid var(--accent); border-radius: var(--radius); padding: var(--space-2) var(--space-3);
  margin: var(--space-3) 0; background: var(--bg-raised); }
.mh-calm { border: 1px solid var(--border); border-left: 4px solid var(--accent); border-radius: var(--radius);
  padding: var(--space-3); margin: var(--space-3) 0; background: var(--bg-raised); }
.mh-calm p { margin: 0 0 var(--space-2); }
.mh-start { border: 1px dashed var(--border); border-radius: var(--radius); padding: var(--space-3); margin: var(--space-3) 0; }
.mh-start h2 { margin-top: 0; font-size: var(--type-h4); }
.mh-section { padding: var(--space-3) 0; }
.mh-section .section-head { margin-bottom: var(--space-1); }
.mh-rule { margin: 0 0 var(--space-3); font-size: var(--type-small); }
.mh-grid { list-style: none; margin: 0; padding: 0; }
.mh-item { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.mh-item > .card { flex: 1 1 auto; }
.mh-item .card h3 { margin: 0; font-size: var(--type-body); line-height: 1.4; }
.mh-item .card h3 a { color: var(--text); overflow-wrap: anywhere; }
.mh-item-foot { display: flex; flex-direction: column; gap: var(--space-2); }
.mh-why { font-size: var(--type-small); }
.mh-why summary { cursor: pointer; color: var(--accent); min-height: 1.5rem; display: inline-flex; align-items: center; }
.mh-why p { margin: var(--space-1) 0 0; color: var(--text-muted); max-width: 60ch; }
.mh-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.mh-form { display: inline-block; margin: 0; }
.mh-action { font: inherit; font-size: var(--type-small); cursor: pointer; color: var(--text); background: transparent;
  border: 1px solid var(--border); border-radius: 999px; padding: 0.35rem 0.9rem; min-height: 2.5rem; }
.mh-action:hover { border-color: var(--accent); color: var(--accent); }
.mh-action:focus-visible, .mh-why summary:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.mh-action--primary { background: var(--accent); color: var(--bg); border-color: var(--accent); font-weight: 700; }
.mh-action--primary:hover { background: var(--accent-strong); color: var(--bg); border-color: var(--accent-strong); }
.mh-update h3 { margin: 0; }
.mh-update .what-changed { list-style: none; margin: var(--space-1) 0 0; padding: 0; font-size: var(--type-small); }
.mh-update .change-line { padding: 0 0 var(--space-1); overflow-wrap: anywhere; }
.mh-caught-up { border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-card);
  padding: var(--space-4) var(--space-3); margin: var(--space-4) 0; text-align: center; }
.mh-caught-up h2 { margin: 0 0 var(--space-2); font-size: var(--type-h3); }
.mh-caught-up p { margin: 0 auto var(--space-2); max-width: 56ch; }
.mh-caught-up .chip-row { justify-content: center; }
.mh-checks { list-style: none; margin: 0 0 var(--space-3); padding: 0; display: grid; gap: var(--space-2);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr)); }
.mh-checks label { display: flex; align-items: center; gap: var(--space-2); min-height: 2.5rem; padding: 0 var(--space-2);
  border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer; overflow-wrap: anywhere; }
.mh-checks input { width: 1.25rem; height: 1.25rem; flex: none; }
.mh-fieldset { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-3); margin: 0 0 var(--space-3); min-width: 0; }
.mh-fieldset legend { padding: 0 var(--space-2); font-weight: 700; }
.mh-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.mh-list li { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2);
  border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-2) var(--space-3); overflow-wrap: anywhere; }
@media (pointer: coarse) { .mh-action { min-height: 2.75rem; padding: 0.45rem 1rem; } }
@media (max-width: 767px) {
  .mh-actions { flex-direction: column; align-items: stretch; }
  .mh-actions .mh-form, .mh-actions .mh-action { display: block; width: 100%; }
  .mh-caught-up { text-align: left; }
}
@media print { .mh-actions, .mh-form { display: none !important; } }

/* ── PROMPT 21: hero ─────────────────────────────────────────────────── */
.game-hero { display: grid; grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
  gap: var(--space-4); align-items: start; margin: var(--space-3) 0; }
.game-hero-media img { width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover;
  border-radius: var(--radius); border: 1px solid var(--border); display: block;
  background: var(--bg-card); }
.game-hero-body { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.game-hero-meta { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap;
  gap: var(--space-1) var(--space-3); font-size: var(--type-small); }
.game-hero-rating, .game-hero-followers { margin: 0; font-size: var(--type-small); }
.game-hero-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.game-mode-toggle { margin: 0; }
@media (max-width: 767px) { .game-hero { grid-template-columns: minmax(0, 1fr); } }

/* ── PROMPT 21: flikar (progressiv förbättring, web/tabs.ts) ─────────── */
.tabs { margin: var(--space-4) 0; }
.tablist { display: flex; flex-wrap: wrap; gap: var(--space-1); margin: 0 0 var(--space-3);
  padding-bottom: var(--space-2); border-bottom: 1px solid var(--border); }
.tab { display: inline-flex; align-items: center; gap: var(--space-1); min-height: 44px;
  padding: 0 var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-xs);
  font-size: var(--type-small); color: var(--text-muted); text-decoration: none; }
.tab:hover { color: var(--text); border-color: var(--accent); text-decoration: none; }
.tabs[data-enhanced] .tab[aria-selected="true"] { background: var(--selected);
  color: var(--bg); border-color: var(--selected); }
.tab-count { font-variant-numeric: tabular-nums; }
.tab-panel { min-width: 0; }
.tab-panel[hidden] { display: none; }
.tab-empty { margin: var(--space-2) 0; }
/* Utskrift: alla paneler, ingen flikrad — samma innehåll som utan JS. */
@media print { .tab-panel[hidden] { display: block; } .tablist { display: none; } }

/* ── PROMPT 21: Simple/Data-läge (web/game-mode.ts) ──────────────────── */
.mode-granular { margin: var(--space-2) 0; }
.mode-granular > summary { cursor: pointer; color: var(--text-muted); font-size: var(--type-small);
  min-height: 44px; display: flex; align-items: center; }
.mode-granular > summary:hover { color: var(--text); }
.metric-explain ul { margin: var(--space-2) 0; padding-left: var(--space-4); }
.metric-explain li { margin: var(--space-1) 0; }
.mechanics-tags { margin: var(--space-2) 0; }
.rtp-range { margin: var(--space-2) 0; }
/* Nyckeltal + källstatus (Översikt/Matematik/Källor): samma block återanvänds. */
.key-metrics { margin: var(--space-3) 0; padding: var(--space-3); background: var(--bg-card);
  border: 1px solid var(--border); border-radius: var(--radius); }
.key-metrics h3 { margin: 0 0 var(--space-2); }
.key-metrics .detail-grid { margin: 0 0 var(--space-2); }
.metric-provenance { margin: var(--space-2) 0; }
.metric-badges { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center;
  margin: 0 0 var(--space-1); }

.universe-note {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3);
  margin: var(--space-3) 0; padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-raised);
  color: var(--text-muted); font-size: var(--type-small); line-height: var(--leading-body);
}
.universe-note p { margin: 0; flex: 1 1 22rem; }
.universe-note .chip { flex: 0 0 auto; }

/* "Utgiven 2026-10-09 · källa S18": one flowing line, the two facts never split in the middle; the dot is decoration */
.card .meta.release-line { display: block; line-height: var(--leading-body); }
.release-line > span { white-space: nowrap; }
.release-line > span + span::before { content: " · "; }
