/* ============================================================
   1. TOKENS — suunnittelujärjestelmän arvot (:root)
   Kaikki värit, etäisyydet, typografiat jne. yhdestä paikasta.
   Komponentit käyttävät vain näitä.
   ============================================================ */
:root {
  /* --- Värit: pinta-hierarkia (Stitch: "layered paper") ---
     Käsittele UI:ta paperipinona. Ei 1px-bordereita osiointiin —
     rajat tehdään taustavärin vaihdolla (tonal shift). */
  --color-bg: #ffffff;              /* focal paper (lowest) */
  --color-surface: #f8f9fa;         /* surface */
  --color-surface-low: #f3f4f5;     /* container-low — taustaosiot */
  --color-surface-mid: #edeeef;     /* container — "desk" */
  --color-surface-high: #e8e9eb;    /* container-high — hover */

  /* --- Värit: teksti & reunat --- */
  --color-text: #191c1d;            /* on_surface — EI pure black */
  --color-muted: #5f6368;           /* sekundaariteksti, metatiedot */
  --color-border: rgba(196, 198, 210, 0.15); /* ghost border — vain kun pakko */
  --color-outline: #c4c6d2;         /* näkyvä reuna (focus, a11y) */

  /* --- Värit: brändi --- */
  --color-primary: #001b44;         /* deep navy — luottamus, auktoriteetti */
  --color-primary-light: #002f6c;   /* primary container — gradienttipari */
  --color-accent: #e67e22;          /* amber-gold — CTA, korostus */
  --color-accent-light: #f0a050;    /* accent container — glow */

  /* --- Värit: tilat --- */
  --color-success: #16a34a;
  --color-warning: #ea580c;
  --color-danger: #dc2626;
  --color-text-inverse: #fff;        /* teksti tummalla taustalla (badge, nappi) */

  /* --- Typografia ---
     Newsreader = toimituksellinen painovoima (otsikot, AI:n löydökset).
     Manrope = tekninen selkeys (leipäteksti, UI).
     Inter = pienet metatiedot, tägit. */
  --font-editorial: 'Newsreader', Georgia, serif;
  --font-body: 'Manrope', system-ui, sans-serif;
  --font-label: 'Inter', system-ui, sans-serif;
  --font-heading: var(--font-editorial);
  --line-height-tight: 1.2;
  --line-height-base: 1.6;          /* generous leading — syvälukeminen */
  --letter-spacing-headline: -0.02em; /* "inked" — tiukka otsikoissa */

  /* --- Etäisyydet (1rem = 16px) --- */
  --space-1: .25rem;
  --space-2: .5rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --space-5: 2rem;
  --space-6: 3rem;
  --space-7: 4rem;                  /* hero-padding mobiili/desktop */

  /* --- Pyöristykset --- */
  --radius-sm: .25rem;
  --radius-md: .375rem;             /* Stitch: napit */
  --radius-lg: 1rem;
  --radius-full: 9999px;            /* pill-shaped chipit */

  /* --- Varjot (navy-tinted, ei puhdasta mustaa) --- */
  --shadow-sm: 0 1px 3px rgba(0, 27, 68, 0.04);
  --shadow-md: 0 4px 12px rgba(0, 27, 68, 0.06);
  --shadow-float: 0 10px 30px rgba(0, 27, 68, 0.06); /* floating: tooltip, modal, FAB */

  /* --- Lasi (glassmorphism — sticky header, modaalit, dropdown) --- */
  --glass-bg: rgba(248, 249, 250, 0.85);
  --glass-blur: 16px;

  /* --- Gradientit --- */
  --gradient-primary: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));

  /* --- Transitiot --- */
  --transition-fast: 120ms ease;
  --transition-base: 200ms ease;

  /* --- Rajat --- */
  --content-max: 1400px;
  /* Luettava rivinpituus leipatekstille. 560px / ~8.8px per merkki (Manrope
     1.1rem) = ~64 merkkia rivilla, eli keskella 60-75 merkin suositusta.
     Arvo on sama kuin heron sisaisilla lohkoilla (.hero-haku, .hero-askeleet,
     .hero-alert) jo ennestaan — selite paattyy nyt samaan linjaan lomakkeen
     kanssa. Ne kayttavat yha literaalia 560px:aa; tokenisointi seuraavalla
     kosketuksella (ei tassa taskissa: vain lisayksia jaettuun app.css:aan). */
  --content-reading: 560px;
  /* Luettava mitta VAAKAKUVAAJALLE (ei tekstille). Kamman kaltaisessa
     kuvaajassa lukija vertaa rivin vasenta ja oikeaa laitaa toisiinsa; kun
     lohko venyy koko containerin leveydelle (1400px), laidat ovat niin
     kaukana etta vertailu ei enaa synny yhdella silmayksella. 720px on noin
     puolet 1440px:n tyopoytaikkunasta ja jattaa >=480px:n kamma-riville
     ~190px palkkia per puoli (nimi 12rem + luvut 2x3rem + gapit). Leveampi
     kuin --content-reading, koska sisalto ei ole proosaa vaan nelisarakkeinen
     rivi. Palaute #220. */
  --content-kuvaaja: 720px;
  /* LOHKOLEVEYSASTEIKKO (#1440, palautteet #356/#267): taysi = --content-max,
     puolikas = yksittaisen lohkon katto kun se ei tarvitse koko leveytta
     (lomake, kapea lista), kapea = ruudukon sarakkeen minimi. Kapea on
     mitoitettu niin etta 1400px:n containeriin mahtuu KAKSI saraketta
     (2 x 480 + gap <= ~1370 sisaltoleveys, 3 x 480 ei) -> vierekkaiset
     lohkot ovat tyopoydalla puolikkaita ja mobiilissa allekkain ilman
     media queryta (.korttiruudukko.lohkot). */
  --lohko-puolikas: 680px;
  --lohko-kapea: 480px;

  /* --- Aksenttiviiva (korttien vasen raita — kategoriaindikaattori) --- */
  --accent-stripe: 4px;

  /* --- Bias-värit (domain, analyysipisteet) --- */
  --color-bias-high:        hsl(0 72% 51%);
  --color-bias-medium:      hsl(38 92% 50%);
  --color-bias-low:         hsl(142 71% 45%);
  --color-bias-neutral:     hsl(220 9% 46%);
  --color-bias-high-bg:     hsl(0 72% 51% / 0.12);
  --color-bias-high-subtle: hsl(0 72% 51% / 0.04);
  --color-bias-high-border: hsl(0 72% 51% / 0.15);
  --color-bias-high-text:   hsl(0 72% 40%);
  --color-bias-medium-bg:   hsl(38 92% 50% / 0.15);
  --color-bias-medium-text: hsl(38 80% 35%);
  --color-bias-low-bg:      hsl(142 71% 45% / 0.12);
  --color-bias-low-subtle:  hsl(142 71% 45% / 0.04);
  --color-bias-low-border:  hsl(142 71% 45% / 0.15);
  --color-bias-low-text:    hsl(142 71% 30%);
  --color-bias-neutral-bg:  hsl(220 9% 46% / 0.12);
  --color-bias-neutral-text: hsl(220 9% 30%);

  /* --- Asteikkovärit kuvaajille (saavutettavuus, Teemun mittaus 8.9.2026) ---
     Lähde: `artefaktit/väripalkit-saavutettavuus.md`.

     MIKSI OLEMASSA: bias-värien liikennevalo (vihreä/oranssi/punainen) romahtaa
     värisokeussimulaatiossa. Deuteranopiassa vihreän ja oranssin kontrasti on
     1,03 — ne ovat sama väri — ja punainen on vain 1,3 irti. Vierekkäisissä
     palkeissa kolme luokkaa näyttää yhdeltä oliivinvihreältä massalta noin
     8 %:lle miespuolisista lukijoista.

     MIKSI EI `--color-bias-*`: ne ovat globaaleja (artikkelikortit, mittarit,
     jakaumat) ja niiden semantiikka on ERI. Bias on arvio (paljonko vinoumaa),
     nämä ovat järjestysasteikko (kuultu -> mainittu -> poissa). Liikennevalo
     väittää lisäksi "punainen = huono" sivulla joka sanoo itse ettei
     kehystysrooli ole arvio puolueesta — ja punainen/vihreä/sininen ovat
     Suomessa puoluevärejä.

     PERIAATE: järjestys koodataan VAALEUDELLA, ei sävyllä. Yksi sävy eri
     vaaleusasteina toimii kaikilla värinäön muodoilla ja harmaasävytulosteessa.
     `-1` on aina TUMMIN pää (asteikon ensimmäinen luokka), `-3` vaalein.

     POHJAOLETUS: paletti on mitoitettu VALKOISELLE pohjalle (--color-bg).
     `prefers-color-scheme`-haaraa ei ole app.css:ssä (0 osumaa, mitattu
     9.9.2026); jos tumma tila joskus lisätään, nämä arvot mitataan uudelleen —
     vaalein askel (1,75:1 valkoista vasten) katoaisi tummalla pohjalla. */
  --color-asteikko-kuultu-1:     #0A3B38;  /* kuultu             — 12,39:1 valkoista vasten */
  --color-asteikko-kuultu-2:     #2E8F88;  /* mainittu, ei kuultu —  3,89:1 */
  --color-asteikko-kuultu-3:     #8FD0C9;  /* poissa              —  1,75:1 (vain pintana, ei tekstinä) */
  --color-asteikko-neutraali:    #4B5563;  /* "ei sovellu" — harmaa, EI osa asteikkoa; 7,56:1 */
  /* Pintakuvio asteikon ULKOPUOLISELLE segmentille (#1321). Sama savy kuin
     pinottujen segmenttien erottimessa (`--color-bg`) mutta OSITTAIN peittava:
     kuvio nayttaa raidoiksi leikatulta segmentilta eika uudelta varilta, ja
     pohjavari pysyy tunnistettavana raitojen valissa. Alpha-syntaksi kuten
     `--color-primary-subtle` alla — raakaa `rgba()`:ta ei ole app.css:ssa. */
  --color-asteikko-kuvio:        hsl(0 0% 100% / 0.55);
  --color-asteikko-vastakkain-1: #0B2E59;  /* aito          — 13,55:1 */
  --color-asteikko-vastakkain-2: #3B7DBF;  /* yksipuolinen  —  4,31:1 */
  --color-asteikko-vastakkain-3: #AFCFEA;  /* muodollinen   —  1,62:1 (vain pintana) */
  /* Sävy on DIVERGOIVA (kaksi suuntaa, ei järjestys) -> sävyero on oikein.
     Purppura–oranssi, EI sini–oranssi: protanopiassa sini–oranssi putoaa
     1,9:ään, purppura–oranssi pysyy 2,6:ssa. */
  --color-savy-krit:        #5E3C99;  /* arvostellaan — 8,13:1, kelpaa myös tekstinä */
  --color-savy-puol:        #E08214;  /* puolustetaan — 2,85:1: PINTANA vain, ei tekstinä */
  --color-savy-krit-teksti: var(--color-savy-krit);  /* sama arvo — pari on symmetrinen käyttöpaikassa */
  --color-savy-puol-teksti: #9A5A00;  /* oranssin tumma variantti — 5,47:1, kelpaa pienenä tekstinä */
  /* Kehystysroolit ovat NIMELLISIÄ (ei järjestystä) -> sävyero sallittu, kunhan
     vaaleus eroaa myös. Varattu kehystysrooleille; `.agent-tag.threat/victim/
     savior` käyttää yhä bias-värejä eikä sitä vaihdettu tässä kierroksessa. */
  --color-rooli-uhka:      #1F5C99;  /* 6,89:1 */
  --color-rooli-uhri:      #BDBDBD;  /* 1,88:1 — pintana vain */
  --color-rooli-pelastaja: #8C4A00;  /* 6,79:1 */

  /* --- Primary-sävyn alphan-variantit (luottamus/info-laatikot, pikakutsu) ---
     Brändin navy (#001b44 = hsl(214 100% 13%)) eri alphoilla. */
  --color-primary-subtle: hsl(214 100% 13% / 0.05);   /* pikakutsu bg, info-laatikko */
  --color-primary-border: hsl(214 100% 13% / 0.10);   /* pikakutsu border */
  --color-primary-bg:     hsl(214 100% 13% / 0.08);   /* .merkki.sininen bg */

  /* --- Domain-merkkien uniikit sävyt (kritisoitu/puolustettu/violetti) ---
     Eri kuin bias-värit — nämä ovat sisältösemantiikkaa, eivät vinoumaa. */
  --color-kritisoitu-bg:  hsl(350 80% 95%);
  --color-kritisoitu-text: hsl(350 70% 35%);
  --color-puolustettu-bg: hsl(160 60% 93%);
  --color-puolustettu-text: hsl(160 50% 30%);
  --color-violetti-bg:    hsl(262 83% 90%);
  --color-violetti-text:  hsl(262 60% 40%);

  /* --- Aktiivinen rivi (propagandataulukko, valitut listarivit) ---
     Eri sävy kuin bias-high (puhtaampi punainen, korkeampi saturaatio). */
  --color-rivi-aktiivinen-bg: hsl(0 84% 60% / 0.06);

  /* --- Vakavuus-tasot vihreä→keltainen→punainen (propaganda) ---
     Eri kuin --color-severity-* (jotka ovat orange-pohjaisia, kategoria-merkki).
     Nämä noudattavat liikennevalo-semantiikkaa propagandariville. */
  --color-vakavuus-mild-bg:     hsl(142 71% 45% / 0.15);
  --color-vakavuus-mild-text:   hsl(142 71% 30%);
  --color-vakavuus-moderate-bg: hsl(38 92% 50% / 0.18);
  --color-vakavuus-moderate-text: hsl(38 80% 35%);
  --color-vakavuus-severe-bg:   hsl(0 72% 51% / 0.18);
  --color-vakavuus-severe-text: hsl(0 72% 40%);

  /* --- Admin-moderointi-merkit (odottaa/hyväksytty/hylätty) ---
     Visuaalisesti vahvempia kuin bias-merkit (hyväksytty teal, hylätty pinkki). */
  --color-odottaa-bg:    hsl(45 93% 47% / 0.18);
  --color-odottaa-text:  hsl(45 80% 30%);
  --color-hyvaksytty-bg: hsl(160 59% 52% / 0.18);
  --color-hyvaksytty-text: hsl(160 59% 25%);
  --color-hylatty-bg:    hsl(0 86% 71% / 0.18);
  --color-hylatty-text:  hsl(0 72% 40%);

  /* --- Tag-pillerit (drilldown, etusivun trending) ---
     Vaalea desaturoitu sininen — eri kuin primary tai bias. */
  --color-tag-bg: hsl(222 100% 92%);

  /* --- Sininen-merkin tekstiväri (= primary, mutta semanttinen alias) ---
     Eri merkkityypeille jotka käyttävät navy:tä tekstinä. */
  --color-sininen-text: hsl(214 100% 13%);

  /* --- Narratiivi-paneelin agentti-tagien sävyt ---
     Threat = punainen (vahvempi kuin bias-high), victim = sininen, savior = vihreä.
     Alpha 0.10 — kevyempi kuin bias-bg (.12) jotta narratiivi-paneelissa ei
     domnoi visuaalisesti. */
  --color-victim-bg: hsl(214 100% 50% / 0.10);
  --color-victim-text: hsl(214 80% 30%);

  /* --- Tone-badge-sävyt (narratiivi-paneeli) ---
     Pelottava/toiveikas/neutraali = bias-värit (jo olemassa).
     Moralisoiva/dramatisoiva/kriittinen ovat uniikkeja sävyjä. */
  --color-tone-moralisoiva-bg:  hsl(27 100% 50% / 0.12);
  --color-tone-moralisoiva-text: hsl(27 80% 30%);
  --color-tone-dramatisoiva-bg: hsl(48 96% 53% / 0.15);
  --color-tone-dramatisoiva-text: hsl(48 80% 28%);
  --color-tone-kriittinen-bg:   hsl(214 100% 50% / 0.12);
  --color-tone-kriittinen-text: hsl(214 80% 30%);

  /* --- Rengas (focus-outline) --- */
  --color-ring: rgba(0, 27, 68, 0.15);

  /* --- Overlay (modaalit, dialog::backdrop) ---
     Navy-tinted (rgba(0, 27, 68, 0.5)) — speksin "Navy-tinted varjot"
     -periaate (varjoissa ei puhdasta mustaa) koskee myös overlayta.
     Aiempi rgba(0, 0, 0, 0.5) modaalissa korvattu navy-tintille. */
  --color-overlay: rgba(0, 27, 68, 0.5);

  /* --- Shadow-laajennus (legacy-komponenteista) --- */
  --shadow-lg: 0 10px 30px rgba(0, 27, 68, 0.08);

  /* --- Domain-tokenit (skeleton, hero, highlight, severity, narrative) ---
     Nama EIVAT ole legacy-jaanteita vaan tarkkasti domain-spesifeja:
     tekstin korostus (framing/loaded/omission), vakavuustasot, narratiivipaneelin
     luotettavuus- ja savytoitu-badget. */
  --color-skeleton:             hsl(210 5% 93%);
  --color-hero-gradient:        hsl(27 100% 29% / 0.05);
  --color-highlight-framing:    hsl(38 100% 85%);
  --color-highlight-loaded:     hsl(0 100% 90%);
  --color-highlight-omission:   hsl(262 83% 90%);
  --color-loaded-text:          hsl(0 60% 35%);
  --color-accent-text:          hsl(27 80% 25%);
  --color-accent-text-dark:     hsl(27 80% 30%);
  --color-accent-bg:            hsl(27 100% 29% / 0.08);
  --color-accent-subtle:        hsl(27 100% 97%);
  --color-accent-border:        hsl(27 80% 80%);
  --color-severity-mild-bg:     hsl(27 100% 29% / 0.15);
  --color-severity-moderate-bg: hsl(27 100% 29% / 0.3);
  --color-severity-severe-bg:   hsl(0 72% 51% / 0.15);
  --color-opinion-badge:        hsl(262 60% 35%);
  --color-secondary:            hsl(40 30% 96%);           /* narratiivipaneelin pehmea tausta */

  /* --- Värit: poliittinen kehystys (suomen mediakonventio) ---
     Vasen-punainen, keskusta-harmaa, oikea-sininen.
     Desaturoitu (ei kirkkaita Material-värejä) — sopii design-ohjeen mukaiseen
     "layered paper" -tunnelmaan. Käytetään sekä drilldown-kaavioissa että
     sidebar-jakaumapalkissa (sama data = sama väri). */
  --color-poliittinen-vasen: #dc2626;
  --color-poliittinen-keskusta: #6b7280;
  --color-poliittinen-oikea: #2563eb;
}

/* ============================================================
   2. POHJA — elementit peruustyylitettyinä
   Pico.classless hoitaa ison osan. Täällä vain overridet ja
   puuttuvat. Tavoite: HTML ilman luokkia näyttää jo hyvältä.
   ============================================================ */

/* --- Pico overridet ---
   Pico.classless asettaa responsiivisen font-sizen (100%->106.25%->112.5%)
   ja body > main/header/footer {max-width: 510..1450px} — molemmat halutaan pois.
   Taustat (glassmorphism ylapalkki, footer-border) pitaa kulkea reunasta reunaan,
   sisallon rajauksen hoitaa .container kutsupaikoissa. Form-spacing pienennetty
   (Picon 0.75/1rem -> 0.5/0.75rem) jotta lomake-elementit ovat tiiviimmat
   artikkelilistan suodattimissa. */
:root {
  --pico-font-size: 100%;
  --pico-form-element-spacing-vertical: 0.5rem;
  --pico-form-element-spacing-horizontal: 0.75rem;
}
@media (min-width: 576px), (min-width: 768px), (min-width: 1024px), (min-width: 1280px), (min-width: 1536px) {
  :root { --pico-font-size: 100%; }
}
body > main,
body > header,
body > footer { max-width: none; padding-left: 0; padding-right: 0; }

body { color: var(--color-text); background: var(--color-bg); font-family: var(--font-body); padding-bottom: var(--space-6); }
img { max-width: 100%; height: auto; }
h1, h2, h3 { font-family: var(--font-heading); letter-spacing: var(--letter-spacing-headline); }
h1 { font-size: 2rem; line-height: var(--line-height-tight); }
h2 { font-size: 1.75rem; line-height: var(--line-height-tight); }
h3 { font-size: 1.125rem; line-height: var(--line-height-tight); }
p { margin: 0 0 var(--space-3); }
a { color: var(--color-primary); }
a:hover { color: var(--color-primary-light); }
ul, ol { padding-left: var(--space-4); }
small, .meta { font-family: var(--font-label); font-size: .75rem; }

[x-cloak] { display: none !important; }  /* Alpine FOUC-esto — ainoa sallittu !important */

/* ============================================================
   3. LAYOUT-UTILITYT (vain jos pakko)
   Yksinkertaiset apurit sivun rakenteelle. Nämä ovat harvinaisia
   — mieluummin `main`, `section`, `article` kuin `.container`.
   Jos Pico:n oletus ei riitä.
   ============================================================ */
.container { max-width: var(--content-max); margin: 0 auto; padding: 0 var(--space-3); }
/* Tilaluokka: kapea keskitetty runko yksittaiselle paatos-/lomakesivulle
   (#1511, palaute #443: /oauth/authorize venyi 1600px:n nakymassa koko
   leveydelle). Sisaltoleveys = luettava rivinpituus; padding lisataan, jotta
   tekstipalsta on tasan --content-reading. Mobiilissa max-width ei sido. */
.container.kapea { max-width: calc(var(--content-reading) + 2 * var(--space-3)); }

/* ============================================================
   4. INTERAKTIIVISET — napit, lomakkeet, taulukot
   Pico hoitaa perustason. Omat tyylit vain kun tarvitaan
   tuotekohtaisia sävyjä tai .btn-variantteja.
   ============================================================ */
.btn { /* variantti jos <button>-oletus ei riitä, esim. linkkinä */ }
form { /* harvoin tarpeen */ }

/* --- taulukko --- */
table { width: 100%; border-collapse: collapse; }
thead th {
  font-weight: 600;
  text-align: left;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-muted);
  padding: var(--space-2) var(--space-3);
  border-bottom: 2px solid var(--color-outline);
}
tbody td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-border);
  font-size: 0.875rem;
}
tbody tr:hover { background: var(--color-surface-mid); }

.taulukko-tiivis { font-size: 0.875rem; margin-bottom: 0; }
.taulukko-tiivis td,
.taulukko-tiivis th { padding: 0.375rem 0.5rem; }

/* Vaakavieritys leveälle datataulukolle (#1156).
   Taulukko jossa on kuusi numerosaraketta ei mahdu 360 px:n näytölle. Ilman
   omaa konttia se työntää KOKO SIVUN leveämmäksi (document.scrollWidth kasvaa),
   jolloin jokainen muukin lohko alkaa vaakavierimään — vika näkyy sivulla jossa
   sitä ei aiheutettu. `min-width` pitää sarakkeet luettavina ja siirtää
   vierityksen tähän konttiin.

   Kutsuja antaa `tabindex="0"` + `role="group"` + `aria-label` (ks.
   Views/yrityksille.php): vieritettävä alue on saavutettava myös näppäimistöllä,
   muuten sen oikea reuna on vain hiiren takana. Ne eivät ole CSS:n asia, joten
   niitä ei voi pakottaa täältä — siksi ne on kirjoitettu myös tähän muistiin.

   Vaihtoehto (rivi = kortti mobiilissa, vrt. `.krediittiloki-taulukko`) hylättiin:
   se sitoo jokaisen solun `data-otsikko`-attribuuttiin ja on oikea kuvio
   tapahtumalokille, jossa rivit luetaan yksi kerrallaan. Tämä taulukko luetaan
   SARAKKEITTAIN (mikä lehti erottuu muista) — korttinäkymä hävittäisi juuri sen
   vertailun jonka takia taulukko on sivulla. */
/* `position: relative` on PAKOLLINEN (#1422): overflow leikkaa absoluuttisen
   jälkeläisen vain jos kääre on sen containing block tai sen sisällä. Solujen
   `.sr-only`-spanit ovat `position: absolute`, ja staattisen kääreen ohi niiden
   containing block oli kääreen ulkopuolella -> ne eivät leikkautuneet vaan
   levittivät koko dokumentin taulukon levyiseksi. Mitattu /politiikka 360 px:
   kääre sw 910 / cw 328, dokumentin hscroll 550 px (320 px: 590). */
.taulukko-vieritys {
  position: relative;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.taulukko-vieritys > table { min-width: 34rem; }

/* Alle 360 px: kavennetaan vierittämättömien taulukoiden solujen vaakapaddingia
   (#1422). 3-sarakkeinen mediataulukko (politiikka.php, topic.php: Media |
   Artikkeleita | Vinouma) ei kutistu min-contentinsa alle — "Artikkeleita" on
   katkeamaton sana — ja Picon 16 px:n solupadding (6 x 16 = 96 px) vei sen yli:
   mitattu 320 px: taulukko 330 px vs kaista 288 -> dokumentti +26 px (341: +5).
   Vierityskääreen taulukot rajataan pois: niillä on min-width ja vieritys on
   tarkoituksellinen. max-width-query poikkeuksena mobile-firstistä: min-width-
   palautus ylikirjoittaisi kaikkien luokallisten taulukoiden oman paddingin. */
@media (max-width: 359.98px) {
  table:not(.taulukko-vieritys > table) :is(th, td) {
    padding-left: var(--space-2);
    padding-right: var(--space-2);
  }
}

/* ============================================================
   5. SIVURAKENNE — ylhäältä alas kuten HTML
   Järjestys seuraa sivun layoutia: header, hero, main, footer.
   Kaikki yhden rakennepalan tyylit (peruustila + variantit +
   sivu-overridet + media queryt) pidetään yhdessä lohkossa.
   ============================================================ */

/* --- ylapalkki ---
   Sticky header glassmorphism-tyylilla. Container rajoittaa leveyden.
   Flexbox: logo vasemmalla, nav keskella, auth/user oikealla.
   Tyopoytanavi (linkit + haku) >=1024px, sita kapeammalla hamburger +
   mobiilivalikko (#1422: kynnys nostettu 768 -> 1024, ks. mittaus alla). */

.ylapalkki {
  position: sticky;
  top: 0;
  z-index: 100;
  padding: var(--space-2) 0;
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  border-bottom: 1px solid var(--color-border);
}

/* Mobiilissa tiukka gap: logo + auth-napit + hamburger eivat mahdu 360 px:iin
   space-3:lla (palaute #142, 11.8.2026 — "venyy yli ruudun"). #142:n gap-korjaus
   laskeutui tasan rajalle (marginaali 0,2 px), joten vika palasi -> #1275
   kutisti `.painike`n paddingin ja fontin (6.9.2026).

   VIERITYSPALKKI KUULUU MITTAUKSEEN: jokaisella oikealla sivulla on pysty-
   vierityspalkki, joten 360 px:n nakymassa navin kaista on 313 px (ei 328).
   Lyhyt testisivu ilman vierityspalkkia antaa vaaran vihrean.

   MITATTU 360 px (srcdoc-iframe, tuotannon app.css, kirjautumaton = ahtain):
     ennen: logo 150,3 + gap 4 + oikea 125,5 + gap 4 + hamburger 44 = 327,8
            -> 313 px kaistalla ylivuoto -15 px
     nyt:   oikea 102,6 -> 304,9 vs 313 = ylivuoto 0, marginaalia 8,1 px.
     Ylivuoto 0 myos 390 / 480 / 767 / 1200 px:ssa ja molemmissa auth-tiloissa.
     8,1 px on se marginaali joka kuluu jos ylapalkkiin lisataan sisaltoa.

   Logo ja hamburger ovat `flex-shrink: 0` (brandinimea ei katkaista, ja
   hamburger on 44x44 WCAG 2.5.5 -tap-target) -> jousto haetaan muualta.

   Kirjautuneena `.oikea` on kapeampi (ikoni + saldo) kuin kirjautumattomana
   ("Kirjaudu" + "Luo tili"), joten mitattu tila on se ahtain.

   `min-width: 0` `.oikea`lle EI korjaa tata, vaikka se on oikea korjaus
   rivittyvalle tekstisisallolle (#1271, .vinoutuneet-aiheet). Mitattu: se
   painaa laatikon 110,7 px:iin mutta sisalto on yha 126 px (`.painike` on
   `white-space: nowrap`) -> nav.scrollWidth nayttaa nollaa mutta "Luo tili"
   liukuu 10,9 px hampurilaisen alle. Piilottaa mittarin, ala lisaa. */
.ylapalkki nav {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.ylapalkki .logo {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
  color: var(--color-text);
  font-weight: 600;
  flex-shrink: 0;
}
.ylapalkki .logo img {
  width: 24px;
  height: 24px;
  border-radius: var(--radius-sm);
}
@media (min-width: 768px) {
  .ylapalkki .logo img {
    width: 32px;
    height: 32px;
  }
}

/* Desktop navigaatio — piilotettu mobiililla */
.ylapalkki .nav-linkit {
  display: none;
  gap: var(--space-3);
}
.ylapalkki .nav-linkit a {
  color: var(--color-text);
  text-decoration: none;
  font-size: .9375rem;
  transition: color var(--transition-fast);
}
.ylapalkki .nav-linkit a:hover {
  color: var(--color-primary-light);
}

/* Oikea puoli: share + auth/user. Tyonnetaan oikealle margin-left:auto.
   Mobiilissa kapea gap jotta auth-napit + hamburger mahtuvat ahtaaseen tilaan. */
.ylapalkki .oikea {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-left: auto;
}

/* Auth-napit (kirjautumaton) — mobiilissa tiukka gap */
.ylapalkki .auth-napit {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

/* Napit ylapalkissa — mobiilissa tiukat paddingit ja pienempi fontti
   jotta "Kirjaudu" + "Luo tili" + hamburger mahtuvat 360px-leveydelle. */
.ylapalkki button {
    margin-bottom: 0;
}
.ylapalkki .painike {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-1);
  border-radius: var(--radius-md);
  font-size: .75rem;
  font-family: var(--font-body);
  cursor: pointer;
  border: none;
  transition: all var(--transition-fast);
  white-space: nowrap;
}
.ylapalkki .painike.ghost {
  background: transparent;
  color: var(--color-text);
}
.ylapalkki .painike.ghost:hover {
  background: var(--color-surface);
}
.ylapalkki .painike.primary {
  background: var(--gradient-primary);
  color: var(--color-text-inverse);
}
.ylapalkki .painike.primary:hover {
  box-shadow: var(--shadow-sm);
}

/* Kayttajavalikko (kirjautunut) */
.ylapalkki .kayttaja {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  position: relative;
}
.ylapalkki .kayttaja-trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin-bottom: 0;
  border-radius: var(--radius-full);
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--color-text);
  transition: background var(--transition-fast), color var(--transition-fast);
}
.ylapalkki .kayttaja-trigger:hover {
  background: var(--color-surface);
  color: var(--color-primary);
}
.ylapalkki .krediitit {
  font-size: .75rem;
  font-family: var(--font-label);
  color: var(--color-muted);
}

/* Kayttajan pudotusvalikko */
.ylapalkki .kayttaja-pudotus {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  min-width: 200px;
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-float);
  padding: var(--space-2) 0;
  z-index: 110;
}
.ylapalkki .kayttaja-email {
  padding: var(--space-2) var(--space-3);
  font-size: .75rem;
  color: var(--color-muted);
  font-family: var(--font-label);
}
.ylapalkki .kayttaja-krediitit {
  display: block;
  padding: var(--space-1) var(--space-3);
  font-size: .875rem;
  color: var(--color-text);
  text-decoration: none;
}
.ylapalkki .kayttaja-pudotus hr {
  margin: var(--space-1) 0;
  border: none;
  border-top: 1px solid var(--color-border);
}
.ylapalkki .pudotus-linkki {
  display: block;
  padding: var(--space-2) var(--space-3);
  font-size: .875rem;
  color: var(--color-text);
  text-decoration: none;
  cursor: pointer;
  background: none;
  border: none;
  width: 100%;
  text-align: left;
  font-family: var(--font-body);
  transition: background var(--transition-fast);
}
.ylapalkki .pudotus-linkki:hover {
  background: var(--color-surface);
}
.ylapalkki .pudotus-linkki.vaara {
  color: var(--color-danger);
}
.ylapalkki .pudotus-linkki.vaara:hover {
  background: var(--color-surface);
}

/* Logout-lomake — ei ulkoista asetteluvaikutusta */
.ylapalkki .logout-lomake {
  margin: 0;
  padding: 0;
}

/* Hamburger-nappi — naytetaan vain mobiililla.
   44x44px tap-target (WCAG 2.5.5 minimum target size). */
.ylapalkki .hamburger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--color-text);
  padding: 0;
  flex-shrink: 0;
}

/* Mobiilivalikko — piilotetaan desktopilla. Rajaus tulee .container:ista. */
.ylapalkki .mobiilivalikko {
  padding: var(--space-2) 0;
}
.ylapalkki .mobiilivalikko a {
  display: block;
  padding: var(--space-2) 0;
  color: var(--color-text);
  text-decoration: none;
  font-size: .9375rem;
  border-bottom: 1px solid var(--color-border);
}
.ylapalkki .mobiilivalikko a:last-child {
  border-bottom: none;
}

/* Alle 360 px (#1422): 320 px:ssa logo 149 + oikea 106 + hamburger 44 + gapit
   = 307 px -> hamburgerin oikea reuna 322,9 eli dokumentti +2,8 px. Logo ja
   hamburger pysyvat flex-shrink:0 (ks. ylla), joten jousto: logon kuvan ja
   nimen valinen gap 8 -> 4 px ja `.painike`n vaakapadding 4 -> 2 px (4 reunaa)
   = -12 px. Vain alle 360: #1275:n 360 px:n mitattu taitto ei muutu
   (max-width-poikkeus mobile-firstista tasta syysta). Lohko on `.painike`n
   perussaannon JALKEEN, koska spesifisyys on sama ja myohempi voittaa. */
@media (max-width: 359.98px) {
  .ylapalkki .logo {
    gap: var(--space-1);
  }
  .ylapalkki .painike {
    padding-left: .125rem;
    padding-right: .125rem;
  }
}

/* Desktop — palautetaan auth-nappien mukava koko ja gap mobiilin tiukennuksen jalkeen. */
@media (min-width: 768px) {
  /* Nav-rivin valjempi gap: mobiilin space-1 on ahtauspakko (#142), ei tyyli. */
  .ylapalkki nav {
    gap: var(--space-3);
  }
  .ylapalkki .oikea {
    gap: var(--space-2);
  }
  .ylapalkki .auth-napit {
    gap: var(--space-2);
  }
  .ylapalkki .painike {
    padding: var(--space-1) var(--space-3);
    font-size: .875rem;
  }
}

/* Tyopoytanavi (linkit + hakukentta) vasta 1024 px:sta — 768-1023 px kayttaa
   hamburgeria (#1422). Tyopoytanavi tarvitsee (mitattu /politiikka, kirjautumaton
   = ahtain, 23.9.2026): padding 32 + logo 157 + nav-linkit 513 + hakukentta min 40
   + oikea 172 + 3 x gap 24 = 986 px. 768:ssa se vuoti 186 px, 911:ssa 44 px.
   1024 px:n ikkunassa vierityspalkin (~17 px) jalkeen kaistaa on ~1007 px ->
   marginaali ~21 px. 1024 on olemassa oleva breakpoint (Pico-lohko r. ~292).
   Jos nav-linkkeja lisataan, mittaa tama uudelleen: jokainen linkki ~60-90 px.
   Hakukentan naytto on samassa kynnyksessa (`.ylapalkki .hakukentta`, osio
   hakukentta) — naiden kahden pitaa aina vaihtaa yhdessa, muuten naytolla on
   0 tai 2 hakukenttaa. */
@media (min-width: 1024px) {
  .ylapalkki .nav-linkit {
    display: flex;
  }
  .ylapalkki .hamburger {
    display: none;
  }
  .ylapalkki .mobiilivalikko {
    display: none; /* Alpine x-show piilottaa mobiilissa, desktop-mediassa ei nayteta */
  }
}

/* --- sivuotsikko ---
   Kehys-komponentti. Nakyy ylapalkin alla, kertoo milla sivulla olemme
   (otsikko + optionaalinen takaisin-linkki + meta + actions). EI omaa
   taustavaria -- ei kilpaile glassmorphism-ylapalkin kanssa. Erotus alas
   tonal-shiftilla (ohut surface-mid-border, ghost). */

.sivuotsikko {
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-4);
}
.sivuotsikko .takaisin {
  display: inline-block;
  font-size: .8125rem;
  font-family: var(--font-label);
  color: var(--color-muted);
  text-decoration: none;
  margin-bottom: var(--space-2);
}
.sivuotsikko .takaisin:hover {
  color: var(--color-primary);
}
.sivuotsikko .rivi {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.sivuotsikko .vasen { min-width: 0; }
.sivuotsikko h1 {
  font-family: var(--font-editorial);
  font-size: 1.5rem;
  line-height: var(--line-height-tight);
  margin: 0;
  word-break: break-word;
}
.sivuotsikko .meta {
  margin-top: var(--space-2);
  font-family: var(--font-label);
  font-size: .8125rem;
  color: var(--color-muted);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  align-items: center;
}
.sivuotsikko .meta .erotin {
  color: var(--color-border);
}
.sivuotsikko .oikea {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  align-items: center;
}

/* Desktop: otsikko ja actions samalle riville */
@media (min-width: 768px) {
  .sivuotsikko {
    padding: var(--space-5) 0;
    margin-bottom: 0;
  }
  .sivuotsikko .rivi {
    flex-direction: row;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--space-4);
  }
  .sivuotsikko h1 {
    font-size: 2rem;
  }
}

/* Rajausrivi — voimassa oleva rajaus nakyvana otsikon alla (rajausrivi.php,
   #1440, palaute #357). Kaista kertoo saman vain rullatessa; tama on lukijan
   ensimmainen tieto siita etta sivu on rajattu. Ei omaa font-sizea: perii
   ymparoivan tekstin (css-velka-merkit.json § font_size_max). Nimilappu
   himmeana, arvo tekstin varilla -> arvo on se mita silma etsii. */
.rajausrivi {
  margin: var(--space-2) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  align-items: baseline;
  color: var(--color-text);
}
.rajausrivi .erotin { color: var(--color-border); }

/* Hakusanasuodatin (#1440, palaute #356): lomake ei tarvitse koko gridin
   leveytta -> puolikas asteikolta. Mobiilissa max-width ei sido. */
.hakusanasuodatin { max-width: var(--lohko-puolikas); }
.rajausrivi-nimi { color: var(--color-muted); }

/* Aikaikkunan valitsin (aikaikkuna-valitsin.php, #1447): preset-linkit
   (`valintalinkit`) + avautuva vapaa vali samalla rivilla, rivittyy kapealla.
   Ei omaa font-sizea (css-velka-merkit.json § font_size_max). Vapaan valin
   kentat kayttavat hakusivun `.hakukentta-aika`-saantoja (sama kenttapari):
   mobiilissa kentta omalle rivilleen, >= 768 px rinnakkain. */
.aikaikkuna-valitsin {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-2) 0 var(--space-4);
}
.aikaikkuna-valitsin .valintalinkit { margin: 0; }
/* Picon details/summary-oletukset (margin-bottom) kumotaan luokkarajatusti. */
.aikavalitsin-vapaa { margin: 0; }
.aikavalitsin-vapaa summary {
  padding: var(--space-1) 0;
  color: var(--color-muted);
}
.aikavalitsin-vapaa[open] > summary { margin-bottom: 0; }
.aikavalitsin-vapaa form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
}
.aikavalitsin-vapaa button {
  width: auto;
  margin: 0;
  padding: var(--space-1) var(--space-3);
}

/* Kontekstikaista — rullaava konteksti-otsikko someruutukaappausta varten
   (kontekstikaista.php, palaute #191). Oma komponenttinsa: mika tahansa sivu
   voi kutsua sen, ei vain sivuotsikkoa kayttavat. Renderoidaan otsikkolohkon
   SIBLINGINA .container-kaareen sisalla -> sticky-containing-block ulottuu
   sivun loppuun ja kaista pysyy pinnattuna koko scrollin ajan.
   Ilmestyy ylapalkin (.ylapalkki, z-index 100) ALLE kun iso otsikko on
   rullannut pois nakyvista (.nakyvissa, app.js togglaa). Nakyy kaikilla
   leveyksilla (myos desktop). Glass-tausta matchaa .ylapalkki:n -> nayttaa
   ylapalkin jatkeelta. */
.kontekstikaista {
  position: sticky;
  top: var(--ylapalkki-korkeus, 52px);
  z-index: 99;                       /* ylapalkin 100 alle -> ei peita navia */
  /* Piilotila: max-height 0 + overflow hidden EI jata layout-aukkoa (sticky
     varaa 0px korkeutta flowssa kunnes .nakyvissa avaa). Vaihtoehto transform
     jattaisi tyhjan sticky-slotin. */
  max-height: 0;
  overflow: hidden;
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  border-bottom: 1px solid var(--color-border);
  transition: max-height var(--transition-base);
}
/* Etuliite + nimi samalla rivilla: flex jotta etuliite ei tyonna nimea omalle
   riville ja ellipsis koskee vain (pitkaa) nimea. Padding kaistan tasolla. */
.kontekstikaista.nakyvissa {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  padding: var(--space-2) 0;
  max-height: 4rem;                  /* riittaa yhdelle padding+teksti-riville */
}
.kontekstikaista .etuliite {
  flex: none;                        /* etuliite ei kutistu, nimi saa ellipsoitua */
  font-family: var(--font-editorial);
  line-height: var(--line-height-tight);
  color: var(--color-muted);         /* hillitty sava -> nimi erottuu painokkaammaksi */
}
.kontekstikaista .nimi {
  font-family: var(--font-editorial);
  font-size: 1.0625rem;
  line-height: var(--line-height-tight);
  color: var(--color-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;           /* pitkat nimet ("Euroopan unioni") katkeavat siististi */
}
/* Voimassa oleva rajaus nimen jaljessa (#1263, palaute #264). EI omaa
   font-sizea: perii kaistan koon, jolloin rajaus jaa toissijaiseksi nimeen
   nahden — mika on myos oikein visuaalisesti. */
.kontekstikaista .rajaus {
  flex: 0 1 auto;
  font-family: var(--font-editorial);
  line-height: var(--line-height-tight);
  color: var(--color-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- hero ---
   Kaikki hero-tyyli yhdessä: perustila, erikoiset (404), desktop.
   Kun lisäät uuden hero-variantin, se tulee tähän lohkoon — ei
   tiedoston alaosaan eikä kommentin "responsive" alle. */

/* Pico.classless lisaa main-elementille block-spacingin — kumoa hero siina */
body > main > .hero {
  margin-top: calc(var(--pico-block-spacing-vertical) * -1);
}

/* Hero — mobile-first: kapea padding + pieni h1 + stacked hakulomake */
.hero {
  padding: var(--space-5) var(--space-3);
  text-align: center;
  background: linear-gradient(180deg, var(--color-hero-gradient) 0%, transparent 100%);
  box-sizing: border-box;
  /* Ankkurihyppy heroon (esim. /analysoi "Syötä osoite" -> #analysoi-alku, #652):
     .ylapalkki on sticky top:0 ja peittaisi heron ylareunan. --ylapalkki-korkeus
     asetetaan ajossa ylapalkin todellisesta korkeudesta (app.js), sama muuttuja
     kuin .kontekstikaista:n top-arvossa; fallback 52px matchaa sita.
     Vrt. .tag-list.alphabetical .aakkos-otsikko (r. ~1653). */
  scroll-margin-top: calc(var(--ylapalkki-korkeus, 52px) + var(--space-3));
}

/* Leveysrajaus isolla naytolla (Teemu 12.8.2026: "isolla ruudulla etusivun
   heron teksti on todella levea").

   RAJAUS KOHDISTUU LAPSIIN, EI `.hero`:on ITSEENSA. Taustagradientti on
   full-bleed (reunasta reunaan) — jos `max-width` laitettaisiin `.hero`:on,
   gradientti kutistuisi keskelle ruutua ja ulkoasu hajoaisi isolla naytolla
   pahemmin kuin ennen korjausta.

   Kaksi tasoa, koska ne ratkaisevat eri ongelman:
   1) `.hero > *` = `--content-max` — sama raja kuin `.container`illa, eli
      hero rivittyy muun sivun kanssa samaan linjaan (Teemun pyynto
      "rajoittaa kuten container rajoittaisi"). Kohdistuu myos `content`-
      slottiin (/analysoi: medialista), joka oli ainoa aidosti 1400px:aa
      leveammaksi venynyt lohko otsikon ja selitteen lisaksi.
   2) Leipateksti kapeampaan luettavaan mittaan, ks. `.hero p` alla. 1400px
      ei riita: etusivun selite on 219 merkkia, joka olisi 1400px:lla
      ~2 rivia poikkeuksellisen pitkaa riviä.

   MOBIILI: molemmat ovat `max-width`, eli ne eivat voi sitoa kapealla
   naytolla — 360px:n ruudulla heron sisaltolaatikko on 328px eika 560px:n
   raja aktivoidu lainkaan. Siksi saannot ovat perustyylissa eivatka
   media queryssa: sama mobile-first-kuvio kuin `.hero-haku`illa ja
   `.hero-askeleet`illa, jotka rajaavat leveytensa samalla tavalla.

   Tarkempi rajaus voittaa taman aina: `.hero figure` (480px),
   `.hero-askeleet` ja `.hero-haku` (560px) ovat elementti- tai
   luokkatarkennettuja ja pysyvat omissa mitoissaan. */
.hero > * {
  max-width: var(--content-max);
  margin-left: auto;
  margin-right: auto;
}
.hero figure { margin: 0 auto var(--space-3); max-width: 480px; }
.hero h1 { font-size: 1.75rem; margin-bottom: var(--space-3); }
.hero p {
  font-size: 1.1rem;
  color: var(--color-muted);
  margin-bottom: var(--space-5);
  /* Taso 2 leveysrajauksesta (ks. `.hero > *` ylla): selite kapeampaan
     mittaan kuin muu hero. Etusivun selite on 219 merkkia — pelkalla
     `--content-max`illa (1400px) se olisi kaksi poikkeuksellisen pitkaa
     rivia, mika on teknisesti "rajattu" mutta yha luettavuudeltaan huono.
     Auto-marginaalit toistetaan tassa tarkoituksella vaikka `.hero > *` jo
     antaa ne: ilman niita `p`-elementin perussaanto (osio 2, `margin: 0 0 …`)
     jattaisi lohkon vasempaan reunaan jos `> *` joskus poistetaan, jolloin
     keskitetty teksti hyppaisi hiljaa ruudun laitaan. */
  max-width: var(--content-reading);
  margin-left: auto;
  margin-right: auto;
}
.hero .title { font-size: 2.5rem; margin-bottom: var(--space-2); }
.hero .caption { color: var(--color-muted); margin-bottom: var(--space-4); }
.hero .btn { /* hero-kohtainen nappi */ }

/* Kavelytysaskeleet (hero-komponentin `askeleet`-prop, array-muoto).
   Jokainen askel on oma <li> jotta rivinvaihto tapahtuu VAIN askelten
   valista: yhtena merkkijonona yhdessa <p>:ssa 360px:n naytto katkaisi
   rivin kesken askelen ja jatti nuolen edellisen rivin loppuun irralleen
   omasta askeleestaan (Teemu 12.8.2026 — 59 % avautumispaivan liikenteesta
   oli mobiilia). Rivitysta ei voi rajata tekstisolmun sisalla, joten korjaus
   on rakenteessa eika CSS-kikassa.
   Numero ja nuoli tulevat CSS-counterista: tekstikirjastossa on pelkka
   askelen sisalto, joten jarjestyksen muutos ei vaadi kasin numerointia. */
.hero-askeleet {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-1) var(--space-2);
  list-style: none;
  max-width: 560px;
  margin: 0 auto var(--space-5);
  padding: 0;
  color: var(--color-muted);
  counter-reset: hero-askel;
}
.hero-askeleet li {
  counter-increment: hero-askel;
  margin: 0;
  /* Pisin askel on ~25 merkkia (~210px) — mahtuu 320px:n naytolle heron
     paddingin jalkeen, joten nowrap ei voi tuottaa vaakavieritysta. */
  white-space: nowrap;
}
.hero-askeleet li::before { content: counter(hero-askel) ". "; }
/* Nuoli kuuluu askeleen SISAAN (::after) — silloin se ei voi rivityksen
   jalkeen koskaan aloittaa uutta rivia, toisin kuin erillisena merkkina
   tekstivirrassa. */
.hero-askeleet li:not(:last-child)::after { content: " →"; }

/* Hero-hakulomake (palvelu, analysoi) — mobile-first: pystysuuntainen pino */
.hero-haku {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 560px;
  margin: 0 auto var(--space-4);
}
/* TYYPPIAGNOSTINEN selektori — ala sido tata `input[type=...]`:iin (#656/#674,
   spec/komponentit.md § analyysijono). Kentta oli `type="url"` mutta muuttui
   heron eristyksessa muotoon `type="text" inputmode="url"`, jolloin vanha
   `.hero-haku input[type="url"]` lakkasi osumasta hiljaa: kentta putosi Picon
   oletustyyliin (42px) ja nappi jai 16px korkeammaksi. Ei virhetta, ei
   testiosumaa — vain vaara ulkoasu tuotannossa (Teemu 11.8.2026).

   Muoto `> input:not([hidden])` on speksin vakiintunut kuvio
   (spec/frontend-css.md § Picon lomakesaannot): `:not()` nostaa
   spesifisyyden (0,2,1):een joka voittaa Picon `input:not([type=checkbox]…)`
   -korkeussaannon (0,1,1), ja lapsivalitsin rajaa saannon lomakkeen omaan
   kenttaan. `height: auto` kumoaa Picon kiinteän korkeuden, jolloin
   flex-stretch tasaa kentan ja napin samaan korkeuteen. */
.hero-haku > input:not([hidden]) {
  flex: 1 1 0%;
  width: 100%;
  font-size: 1rem;
  padding: var(--space-3) var(--space-4);
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  transition: border-color var(--transition-fast);
  height: auto;
  min-width: 0;
}
.hero-haku > input:not([hidden]):focus {
  border-color: var(--color-primary);
  outline: 2px solid var(--color-ring);
}
.hero-haku .painike {
  flex: 0 0 auto;
  white-space: nowrap;
  width: 100%;
}

/* Hero-napin sisainen ikoni/teksti-vaihto:
   Mobiilissa kapea (481-640) nayttaa ikonin, muuten teksti. */
.hero-btn-icon { display: none; }
.hero-btn-check {
  display: inline-flex;
  align-items: center;
  font-size: 1.25rem;
  font-weight: 700;
  animation: check-pop 0.4s ease-out;
}
@keyframes check-pop {
  0%   { transform: scale(0); opacity: 0; }
  50%  { transform: scale(1.3); }
  100% { transform: scale(1); opacity: 1; }
}

/* --- medialista (domain-komponentti) ---
   Lista medialahteista. Sama komponentti kolmessa kayttopaikassa:
   analyze.php hero (sisaiset linkit, grid), alapalkki (ulkoiset linkit,
   kompakti), /mediat-arkistosivu (sisaiset linkit, grid).
   Variantit tilaluokkina: .medialista.grid, .medialista.kompakti.
   Korvasi .hero-tags-luokan 9.5.2026 (#401). */
.medialista {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.medialista li { margin: 0; padding: 0; list-style: none; }

/* Grid-variantti: .merkki-tagien rivi (analyze hero, /mediat). Keskitetty hero-kontekstissa. */
.medialista.grid { justify-content: center; }
.medialista.grid a.merkki { text-decoration: none; }

/* Kompakti-variantti: tekstilinkit (footteri). Vain listan oma tiheampi gap —
   linkin tyyli on jaetussa .kompakti-linkki-luokassa alla. */
.medialista.kompakti {
  gap: var(--space-1) var(--space-2);
}

/* Kompakti tekstilinkki — JAETTU PERUSLUOKKA, ei medialistan oma (#1129).
   Kayttajat: medialista (nakyma='kompakti': alapalkki + /koosta-sivun
   "Koosta median etusivu" -widget) ja koosta-kategoriat. Tyyli on
   tarkoituksella scopettamaton: sama UI-palikka = sama toteutus, ei
   rinnakkaista kopiota (CLAUDE.md § Frontend-komponenttien uusiokaytto).
   Jos taman scopettaa takaisin .medialista:n alle, seuraava kayttaja
   kopioi deklaraatiot — se on juuri se drift jota saanto estaa.

   De-scopeus laskee spesifisyyden (0,3,0) -> (0,1,0). Tarkistettu ettei
   yksikaan kilpaileva saanto nouse ohi: Pico classless karii omat
   a-tyylinsa :where():iin (spesifisyys 0 by design), ja app.css:n omat
   `a` / `a:hover` ovat (0,0,1) / (0,1,1) — molemmat haviavat yha
   (0,1,0):lle ja (0,2,0):lle. Alapalkin `.alapalkki nav a` (0,2,1)
   deklaroi samat neljä ominaisuutta, mutta EI osu tahan: footterin
   medialista on <nav>:in SISARUS, ei sen sisalla. */
.kompakti-linkki {
  font-family: var(--font-label);
  font-size: 0.8125rem;
  color: var(--color-muted);
  text-decoration: none;
  transition: color var(--transition-fast);
}
.kompakti-linkki:hover {
  color: var(--color-text);
}

/* Footter-konteksti: pinotaan disclaimerin alle, otsikko erottuu nav:sta */
.alapalkki .medialista {
  margin-bottom: var(--space-3);
}
.alapalkki .mediat-otsikko {
  display: block;
  font-family: var(--font-label);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-muted);
  margin-bottom: var(--space-1);
}

/* Mediat-arkistosivu (#401, 9.5.2026) — kortit ruudukossa.
   Hyodyntaa olemassa olevaa .kortti.compact-komponenttia. */
.mediat-arkisto {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-3);
}
.media-arkisto-kortti h3 { margin-top: 0; margin-bottom: var(--space-2); }
.media-arkisto-kortti h3 a { text-decoration: none; }
.media-arkisto-kortti h3 a:hover { text-decoration: underline; }
.media-arkisto-kortti .meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-1);
}

/* Kortin kompaktit jakaumapalkit (task #560): poliittinen, vinouma, laatu.
   Leima jokaisen palkin ylle (kaanteinen variskaala vinouma/laatu). */
.media-tilastot { margin-top: var(--space-3); }
.media-tilastot .info-teksti { display: block; }
.media-tilastot .vinouma-palkki,
.media-tilastot .laatu-jakauma,
.media-tilastot .poliittinen-suuntaus-palkki {
  margin: var(--space-1) 0 var(--space-2);
}
.media-tilastot .tilasto-rivi:last-child .vinouma-palkki,
.media-tilastot .tilasto-rivi:last-child .laatu-jakauma,
.media-tilastot .tilasto-rivi:last-child .poliittinen-suuntaus-palkki {
  margin-bottom: 0;
}

/* Hero-aluueen ilmoitus + hidas odotus-vihje (analyze-sivu) */
.hero-alert {
  max-width: 560px;
  margin: 0 auto var(--space-3);
  text-align: left;
}
.hero-wait-hint {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
/* analyze.php hero-wait-hint sisaspan + warning-alertin p: korvaa text-sm */
.hero-wait-hint span { font-size: 0.875rem; }
.hero-alert.warning p { font-size: 0.875rem; }

/* Tabletti (481px+): rivilayout + ikonimuoto napissa, teksti piilossa */
@media (min-width: 481px) {
  .hero-haku { flex-direction: row; }
  .hero-haku > input:not([hidden]) { width: 0; }
  .hero-haku .painike { width: auto; padding: var(--space-3); }
  .hero-haku .painike .hero-btn-text { display: none; }
  .hero-haku .painike .hero-btn-icon { display: inline-block; vertical-align: middle; }
}

/* Leveampi mobile/tabletti (641px+): teksti takaisin nakyviin napissa, ikoni piiloon */
@media (min-width: 641px) {
  .hero-haku .painike { padding: var(--space-2) var(--space-4); }
  .hero-haku .painike .hero-btn-text { display: inline; }
  .hero-haku .painike .hero-btn-icon { display: none; }
}

/* Desktop (768px+): leveampi hero-padding + isompi title/caption */
@media (min-width: 768px) {
  .hero { padding: var(--space-7) var(--space-4); }
  .hero h1 { font-size: 2.5rem; }
  .hero .title { font-size: 3.5rem; }
  .hero .caption { font-size: 1.25rem; }
}

/* Sivukohtainen erikoishero */
body.404 .hero { padding: var(--space-6) 0; }
body.404 .hero .title { color: var(--color-muted); }

/* --- main + sisäinen rakenne --- */
main { padding: var(--space-4) 0; }
main section { margin-bottom: var(--space-5); }
article { margin-bottom: var(--space-4); }

/* --- alapalkki ---
   Ghost-border ylareunaan, muted-tekstit, linkit flexilla. */

.alapalkki {
  border-top: 1px solid var(--color-border);
  padding: var(--space-5) 0 var(--space-4);
  margin-top: var(--space-5);
}

.alapalkki .disclaimer {
  display: block;
  color: var(--color-muted);
  font-family: var(--font-label);
  font-size: .75rem;
  margin-bottom: var(--space-3);
}

.alapalkki nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}
.alapalkki nav a {
  font-size: .8125rem;
  color: var(--color-muted);
  text-decoration: none;
  transition: color var(--transition-fast);
}
.alapalkki nav a:hover {
  color: var(--color-text);
}
.alapalkki nav a:not(:last-child)::after {
  content: "|";
  color: var(--color-border);
  font-size: .75rem;
  margin-left: var(--space-2);
}

.alapalkki .tekijanoikeus {
  display: block;
  color: var(--color-muted);
  font-family: var(--font-label);
  font-size: .6875rem;
}

/* Desktop */
@media (min-width: 768px) {
  .alapalkki nav {
    gap: var(--space-2);
  }
  .alapalkki nav a:not(:last-child)::after {
    content: none;
  }
}

/* ============================================================
   6. KOMPONENTIT — itsenäiset palikat
   Komponentit toimivat missä tahansa DOM:ssa. Ei oletuksia
   äitiluokasta. Leveys täyttää aina äitinsä. Kaikki komponentin
   perus + variantit + sivu-overridet + media queryt yhdessä lohkossa.
   ============================================================ */

/* --- info-teksti + tyhja-tila (#790) ---
   Selittava alateksti ja tyhjan tilan viesti. Molemmilla oli 7 ja 6
   kontekstisaantoa mutta EI perussaantoa -> 49 + 27 kayttopaikasta iso osa
   renderoitiin Picon leipatekstina (1rem, --color-text). Yksi ryhmitelty
   saanto, kontekstisaannoista poistettu toisto (auditointi H4-css-15).
   Vain font-size + color: marginaalit tulevat .mt- ja .mb-utiliteista
   (osio 7, sama spesifisyys 0,1,0 -> myohempi voittaa) ja komponenttien
   omista saannoista. HUOM: ei "*" + "/" -paria kommentissa, se sulkisi sen. */
.info-teksti,
.tyhja-tila {
  font-size: .875rem;
  color: var(--color-muted);
}

/* <small class="info-teksti"> sailyttaa osion 2 pienemman koon (.75rem).
   Ilman tata luokka KASVATTAISI 11 elementin kokoa: mediat.php:n
   .media-tilastot-leimat, article.php:n mittarilabelit, tilastokytkos-
   laatikon alaviite. Luokan tehtava on muted-selite, ei koon nosto. */
small.info-teksti { font-size: .75rem; }

/* --- hakukentta (kehys-komponentti, #706) ---
   Sivuston haun lomake. Toimii missa tahansa aidissa: tayttaa leveyden,
   ei oleta ylapalkkia. Kontekstikohtaiset kavennukset tassa samassa
   lohkossa (frontend-css.md § 8: kaikki komponentin tyylit yhdessa).

   ⚠️ Selektorit ovat tyyppiagnostisia (.hakukentta input) — EI
   input[type="search"]. Tyyppiin sitominen katkeaa hiljaa jos input-tyyppi
   vaihtuu (#656/#674-oppi analyysijonon kentasta). */
.hakukentta {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  width: 100%;
  margin: 0;

  /* Lisakentat lomakkeen sisalla (#711 koostehaun lahdevalinta). flex-wrap on
     turvallinen lisays: kahden lapsen lomakkeet (ylapalkki, /haku) mahtuvat
     yhdelle riville kuten ennenkin — vain kolmas lapsi kaareytyy omalle. */
  flex-wrap: wrap;

  /* Pico piirtaa type=search-kenttaan oman suurennuslasin taustakuvana
     (`background-image: var(--pico-icon-search)`). Napin ikoni riittaa —
     kahta lasia ei nayteta. Nollaus tehdaan Picon omalla muuttujalla eika
     background-image-saannolla: Picon selektori on
     `input:not([type=checkbox],...)[type=search]` eli spesifisyys (0,2,1)
     (`:not()` perii argumenttinsa spesifisyyden), jota `.hakukentta input`
     (0,1,1) ei voita. Muuttuja periytyy inputiin, jolloin Picon oma saanto
     resolvoituu arvoon `none` — ei spesifisyyskisaa lainkaan. */
  --pico-icon-search: none;

  /* 3) RYHMAKUORI (palaute #144, 11.8.2026). Kolmas kerros samaa #744:n
     juurisyyta: `[role=search]` ei ole Picossa pelkka landmark vaan
     ryhmakomponentti. Kaksi edellista kerrosta neutraloivat ryhmatulkinnan
     LAPSILTA (input, nappi); tama neutraloi lomakkeen OMAN kuoren:

       [role=search] { border-radius: var(--pico-border-radius);   -> 5rem
                       box-shadow: var(--pico-group-box-shadow, 0 0 0 transparent);
                       transition: box-shadow ... }
       [role=search]:has(input:not([type=submit],[type=button]):focus)
                     { --pico-group-box-shadow: var(--pico-group-box-shadow-focus-with-input) }

     Mitattu tuotannon CSS:lla 360x674-viewportissa (CDP,
     Emulation.setFocusEmulationEnabled): kun paivamaarakentta sai fokuksen,
     lomakkeen laskettu tyyli oli `box-shadow: rgb(207,213,226) 0 0 0 1px`
     ja `border-radius: 80px`. Koska `lisakentat`-propsi (#711, #728) tuo
     lahdevalinnat ja aikakentat SAMAN lomakkeen sisalle, kuori piirtyi koko
     hakualueen ymparille 80 px:n pyoristyksella. Teemun sanoin: "kun pvm
     kentan aktivoi, joku pyoristettyreuna ilmestyy hassusti taustalle
     ympyroiden koko ns. hakualueen".

     ⚠️ MITTAUS VAATII TRANSITION ODOTUKSEN. `transition: box-shadow` tekee
     valittomasti focus():n jalkeen luetusta arvosta transition ALKUARVON
     (transparent) — ensimmainen mittaus antoi valheellisen vihrean. Jos
     todennat tata uudelleen, odota ~600 ms ennen getComputedStylea.

     Kumous ei tarvitse spesifisyyskisaa: `.hakukentta` (0,1,0) on tasoissa
     `[role=search]`:n (0,1,0) kanssa, ja app.css ladataan Picon jalkeen
     (kehys/Views/components/layout.php) -> lahdejarjestys ratkaisee.
     Nollataan itse ominaisuus eika `--pico-group-box-shadow` -muuttuja,
     koska muuttujan asettava `:has()`-saanto on (0,3,1) ja voittaisi
     komponenttiluokan. `role="search"` SAILYY (ruudunlukija tarvitsee sen);
     vain visuaalinen ryhmatulkinta neutraloidaan — sama linja kuin
     kerroksissa 1-2. */
  box-shadow: none;
  border-radius: 0;
}
/* Hakusanakentan oma tyyli. LAPSIVALITSIN ON PAKOLLINEN (palaute #136,
   11.8.2026): jalkelaisvalitsimena (`.hakukentta input`) tama vuoti
   `.hakukentta-lisa`:n sisalle ja tyylitti lahdevalinnan checkboxit
   pillereiksi — `border-radius: var(--radius-full)` + vaakapadding + `flex: 1`.

   Pahin seuraus ei ollut muoto vaan VALINTATILAN KATOAMINEN. Picon
   `[type=checkbox]:checked` (0,2,0) asettaa `background-image`n (valkoinen
   check-SVG) ja MUUTTUJAN `--pico-background-color`, mutta itse taustavarin
   maalaa `input,select,textarea` (0,0,1) — jonka tama saanto (0,1,1) voitti
   arvolla `background: transparent`. Lopputulos: valkoinen check valkoista
   taustaa vasten = rastia ei nay lainkaan. Koostehaku veloittaa krediitteja
   valittujen lahteiden maaran mukaan (#787), joten vaara kasitys valinnasta
   on vaara kasitys hinnasta.

   Sama ansa kuin #744:ssa (ks. `.hakukentta-aika`-kommentti alla) ja
   frontend-css.md § "Kun nostat spesifisyytta, tarkista mika muu nojasi
   matalampaan": combinator ei muuta spesifisyytta mutta estaa osuman
   sisakkaisiin lohkoihin. HUOM: kun tama saanto rajattiin, `.hakukentta-aika
   input` (#728) menetti perimansa arvot -> ne on toistettu siella
   eksplisiittisesti. Jos lisaat tanne uuden deklaraation, tarkista tarvitseeko
   se saman kompensaation. */
.hakukentta > input {
  flex: 1;
  min-width: 0;
  margin: 0;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-outline);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-text);
  font-family: var(--font-body);
}
/* Picon ylikirjoitukset inputille. Kaksi eri juurisyyta, sama (0,3,1)-kuvio.

   ⚠️ MITOITA KORKEINTA OSUVAA HAARAA VASTAAN, ALA ENSIMMAISTA LUKEMAASI.
   Picon saantolohkot ovat pitkia valitsinLISTOJA, ja listan jokaisella
   haaralla on OMA spesifisyytensa — voittaja on korkein osuva, ei se joka
   sattuu olemaan luettavimmin kirjoitettu. Sama lohko sisaltaa esim.
   seka `[role=search]>:not(:last-child)` (0,2,0) etta
   `[role=search] input:not([type=checkbox],[type=radio]):not(:last-child)`
   (0,3,1) — jalkimmainen on se jota vastaan pitaa mitoittaa. Tama maksoi
   #744:ssa yhden deploy-kierroksen: (0,2,1)-tasaus riitti neljaan
   ominaisuuteen mutta havisi kahteen (inputin oikeat kulmat, napin
   margin-left).

   Spesifisyys nostetaan TOISTAMALLA KOMPONENTTILUOKKA (`.hakukentta.hakukentta`)
   eika lisaamalla toista `:not([attribuutti])`-suodinta. Syy: attribuuttiin
   sidottu tayte lakkaa osumasta juuri siina tilassa jonka attribuutti kuvaa.
   `:not([disabled])` olisi pudonnut pois heti kun nappi disabloidaan — ja
   komponentin oma docblock lupaa juuri sellaisen laajennuksen ("live-haku"),
   jolloin layout hajoaisi latauksen ajaksi. Luokan toisto ei riipu mistaan
   tilasta: se osuu aina kun komponentti on olemassa. Tarkeyslippua ei tarvita.

   Lahdejarjestys ratkaisee tasapelit (app.css ladataan Picon jalkeen, ks.
   kehys/Views/components/layout.php). `[hidden]` on tyyppiagnostinen —
   selektoria EI sidota `input[type="search"]`:iin (#656/#674). Lapsivalitsin
   rajaa saannot lomakkeen omaan hakukenttaan: `.hakukentta-lisa`:n sisalto
   (#711 lahdevalinta, #728 aikajanne) jaa koskematta.

   1) RYHMATULKINTA (#744). Pico kohtelee `role="search"`-lomaketta
      RYHMAKOMPONENTTINA — samaa saantoperhetta kuin `[role=group]`, ei pelkkaa
      saavutettavuuslandmarkia. Se liimaa lapset yhdeksi kontrolliksi:
      `[role=search] input:not(...)` antaa `flex: 1 1 auto`, jolloin inputin
      peruskoko on Picon `width: 100%` eli koko kontin leveys → `flex-wrap`
      tyonsi lahetysnapin omalle rivilleen. `flex-basis: 0` lopettaa taman.
      `input:not(...):not(:last-child)` (0,3,1) litistaa inputin oikeat kulmat
      0:ksi ja `>:first-child` (0,2,0) pyoristaa vasemmat 5rem:iin — pilleri
      hajosi group-lookiksi. `role="search"` SAILYY (ruudunlukija tarvitsee
      sen); vain visuaalinen ryhmatulkinta neutraloidaan.

   2) IKONIVARAUS. Sama type=search -saanto joka piirsi suurennuslasin (ks.
      --pico-icon-search -kommentti ylla) varaa sille myos tilaa
      `padding-inline-start: calc(... + 1.75rem)`. Kun ikoni on nollattu,
      varaus on turhaa kaistaa. */
.hakukentta.hakukentta > input:not([hidden]) {
  flex: 1 1 0;
  border-radius: var(--radius-full);
  padding-inline-start: var(--space-3);
}
.hakukentta button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  margin: 0;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-text);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
/* Hover koskee vain ikoninappia. Tekstillinen nappi saa kuorensa kutsujan
   luokasta (`nappilisaluokka`, esim. `painike maksullinen`), ja tama saanto
   (0,2,1) voittaisi sen taustavarin (0,2,0) → sininen nappi valahtaisi
   harmaaksi hoverissa vaalean tekstin kanssa. Rajaus varianttiluokalla eika
   kutsujan luokalla: kehys ei saa tuntea `.painike`-variantteja. */
.hakukentta button:not(.tekstillinen):hover {
  background: var(--color-surface-high);
  color: var(--color-primary);
}
/* Picon ryhmatulkinta napille — sama juurisyy ja sama (0,3,1)-kuvio kuin
   inputilla (ks. kommentti ylla: korkein osuva haara + luokan toisto).
   Kumottavat haarat ja niiden spesifisyydet:
     `[role=search]>*`                            (0,1,0) flex-grow: 1
        → nappi venyi taysleveaksi omalle rivilleen; sen `width: 40px` ei
          kadonnut, grow vain ohitti sen
     `[role=search] [type=submit]`                (0,2,0) width: auto
     `[role=search]>:not(:first-child)`           (0,2,0) vasemmat kulmat 0
     `[role=search] [type=submit]:not(:first-child)` (0,3,0) margin-left: -1px

   Huomaa viimeinen: `type="submit"` -haara on korkeampi kuin saman listan
   `button:not(:first-child)` (0,2,1), ja juuri se jai voittamaan kun tama
   saanto oli viela (0,2,1). Ilman naita ikoninappi renderoityy inputin
   jatkeena, ei erillisena pyoreana painikkeena. */
.hakukentta.hakukentta > button:not([hidden]) {
  flex: 0 0 auto;
  width: 40px;
  border-radius: var(--radius-full);
  margin-left: 0;
}

/* Variantti: lahetysnappi jossa on NAKYVA teksti (`nappiteksti`-props, #747).
   Kiintea 40px on ikoninapin mitta — tekstin kanssa nappi tarvitsee leveytta
   ja vaakapaddingin. Vareja tama ei aseta: ne tulevat kutsujan luokasta
   (`nappilisaluokka`), koska kehys ei tieda onko toiminto maksullinen.

   Spesifisyys: kumottava saanto on `.hakukentta.hakukentta > button:not([hidden])`
   (0,3,1) — ks. Picon ryhmatulkinta-kommentti ylla. Tama toistaa
   komponenttiluokan JA lisaa varianttiluokan → (0,4,1). Sama luokantoisto-
   kuvio kuin muualla lohkossa: se ei riipu mistaan elementin tilasta
   (`:not([disabled])` olisi pudonnut pois juuri disabloidusta napista). */
.hakukentta.hakukentta > button.tekstillinen:not([hidden]) {
  width: auto;
  padding: var(--space-1) var(--space-3);
}

/* Variantti: hakutulossivun oma kentta — leveampi, ei ylapalkin kavennusta. */
.hakukentta.iso {
  max-width: 32rem;
}

/* Lisakentat lomakkeen sisalla (#711 koostehaun lahdevalinta).
   HUOM: .hakukentan oma `flex-wrap: wrap` asuu komponentin perussaannossa
   (frontend-css.md § 8: kaikki komponentin tyylit yhdessa lohkossa). */
.hakukentta-lisa {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-1);
  padding-left: var(--space-3);
}
.lahde-valinta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0;
  font-size: .875rem;
  cursor: pointer;
}
/* Checkbox jaa TARKOITUKSELLA Picon natiiviin ulkoasuun: rastitettuna Pico
   maalaa taustan (`--pico-primary-background`) ja piirtaa valkoisen check-SVG:n.
   Se on yksiselitteisin mahdollinen valintaindikaattori — omaa taustaa TAI
   `background`-shorthandia ei saa asettaa tanne, koska shorthand nollaisi
   Picon `background-image`n ja rasti katoaisi (juuri nain kavi #136:ssa
   baselohkon vuodon kautta, ks. `.hakukentta > input` -kommentti).
   `margin: 0` kumoaa Picon `margin-inline-end: .5em` -varauksen; valistys
   tulee labelin `gap`ista. `flex: 0 0 auto` estaa kutistumisen kapealla
   rivilla — Picon `width: 1.25em` on flex-itemin perusleveys, ei minimi. */
.lahde-valinta input {
  flex: 0 0 auto;
  margin: 0;
}
/* Valinta erottuu myos ILMAN varia: sininen tayttö on ensisijainen signaali,
   mutta se katoaa harmaasavytulostuksessa, korkean kontrastin tiloissa ja
   varisokealta. Rakenne `<input> + <span>` (koostehaku.php) sallii taman
   ilman `:has()`-tukea. */
.lahde-valinta input:checked + span {
  font-weight: 600;
}

/* Aikajanteen kentat hakutulossivulla (#728). Sama rakenne kuin
   .lahde-valinta: label kaaria selitteen ja kentan yhdeksi pariksi.

   MOBIILISSA KUMPIKIN LABEL OTTAA OMAN RIVINSA (palaute #144, 11.8.2026).
   Teemu Androidilla 360x674: "Paivamaararajauslaatikot toimii huonosti
   mobiilissa, paivamaara ei nay. Pitaisi rivittaa kentat, etta tilas
   enempi sivusuunnassa."

   Aiempi `flex: 0 0 auto` piti kentan natiivissa oletusleveydessaan, joka
   on SELAINKOHTAINEN: desktop-Chromen mobiiliemulaatiossa se on 167 px ja
   mahtuu (mitattu 11.8.2026 — sisalto ei leikkautunut), mutta Androidin
   natiivi paivamaaravalitsin on leveampi eika sen sisalto mahtunut.
   Emulaatio ei renderoi natiiveja lomakekontrolleja Androidin tavalla,
   joten tata EI voi todentaa emulaatiolla — siksi korjaus tehdaan
   rakenteellisesti eika mittaamalla kentan leveytta:

     - label `flex: 1 1 100%`  -> oma rivi, koko kaytettavissa oleva leveys
     - input `flex: 1 1 auto`  -> venyy rivin loppuun selitteen jalkeen

   Nain kentan leveys ei enaa riipu selaimen oletuksesta vaan rivin
   leveydesta: mita ikina natiivikontrolli haluaakin nayttaa, tilaa on koko
   rivin verran. `min-width: 0` sallii kutistumisen jos natiivileveys on
   suurempi kuin rivi (flex-itemin oletus `min-width: auto` estaisi sen ja
   palauttaisi ylivuodon).

   Desktopilla palautetaan entinen rinnakkaisasettelu: siella tilaa on ja
   alkaen+asti vierekkain on tiiviimpi. Mobile-first (frontend-css.md § 7):
   perustyyli = mobiili, media query lisaa desktopin.

   Spesifisyys: baselohkon KUMPIKAAN saanto ei osu tanne — molemmat ovat
   lapsivalitsimia lomakkeen suoriin lapsiin (`.hakukentta > input` ja
   `.hakukentta.hakukentta > input:not([hidden])`), ja nama inputit ovat
   `.hakukentta-lisa`:n sisalla.

   ⚠️ SIKSI KENTTA EI PERI MITAAN — kaikki tarvittava on toistettava tassa.
   Rivit `margin`/`border`/`background`/`color`/`font-family` EIVAT ole
   koristetta: ilman niita Pico palauttaa omat oletuksensa (mm.
   `margin-bottom: 1rem` -> kentat irtoavat rivista, valkoinen tayttö ->
   lohko lakkaa olemasta lapinakyva). Ne perittiin baselohkosta 11.8.2026
   asti, jolloin base rajattiin lapsivalitsimeksi #136:n takia.
   `background: transparent` on shorthand kuten baselohkossakin: se ei vie
   Picon kalenteri-ikonia, koska ikonin asettava date-saanto on (0,2,1). */
.hakukentta-aika {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0;
  cursor: pointer;
  flex: 1 1 100%;
}
.hakukentta-aika input {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-outline);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text);
  font-family: var(--font-body);
}
@media (min-width: 768px) {
  .hakukentta-aika {
    flex: 0 0 auto;
  }
  .hakukentta-aika input {
    flex: 0 0 auto;
  }
}

/* Koostehaun tuloskontti (#711). Latausvaiheessa sisalto on yksi info-rivi;
   fragment korvaa sen artikkelilistalla.

   ⚠️ TASSA KUMOTAAN KAKSI PICON `[aria-busy=true]`-SAANTOA. Ala poista
   `aria-busy`-attribuuttia nakymista sen sijaan: se on ruudunlukijan tilatieto
   (`aria-live`-parina) ja `/js/koostehaku.js` kytkee sita (rivit 127 ja 148).
   Kumottava on Picon VISUAALINEN tulkinta, ei semantiikka.

   1) `::before` — Pico piirtaa OMAN latausikoninsa jokaiselle aria-busy-
      elementille (`background-image: var(--pico-icon-loading)`). Kontti
      renderoi sisalleen jo `latausindikaattori`-komponentin, joten ruudulla
      oli KAKSI pyorivaa ikonia (Teemun palaute 2.9.2026). Sama vikaluokka kuin
      palaute #135 (9.8.2026) jonka jalki on koostehaku.php:n kommentissa —
      silloin toinen "spinneri" oli staattinen icon('spinner'), nyt se tulee
      kirjastosta. Latausikoni kuuluu komponentille, ei kontille.

      Miksi `content: none` eika spec/frontend-css.md § "Picon oma custom
      property" (`--pico-icon-loading: none`): mitattu 2.9.2026 headless
      Chromella 360px-viewportissa — muuttujalla ikoni katoaa mutta
      pseudo-laatikko JAA (`inline-block`, 16px + 8px margin), jolloin teksti
      alkaa yha 24px sisennettyna. `content: none` estaa laatikon synnyn:
      siirtyma 24px -> 0px, latausikoneita 2 -> 1.
      Spesifisyys (0,2,1) > Picon (0,1,2) — todennettu selaimesta, ei laskettu.

   2) `white-space` — Pico asettaa aria-busy-elementille `nowrap`, joka
      periytyy latausindikaattorin tekstiin. 360px:lla "Haetaan medioista —
      tama voi kestaa puoli minuuttia." ei rivittynyt: kontti vuoti 100px yli
      ja koko sivu sai vaakavierityksen (scrollWidth 444 vs 360). */
.koostehaku-tulokset[aria-busy="true"] {
  opacity: .7;
  white-space: normal;
}
.koostehaku-tulokset[aria-busy="true"]::before {
  content: none;
}

/* Kategoriakoosteen valinta (#947, palaute #210; ilme uusittu #1129).
   Rivi maksullisia LINKKEJA: klikkaus vie kategoriasivulle
   (`?kategoria=X&hae=1`), joten elementti on <a> eika <button> — navigaatio,
   ei lomakkeen toiminto.

   ILME = ALAREUNAN MEDIALINKKIWIDGETTI. Teemu 31.8.2026: *"ehka muuta ne vaan
   tekstilinkeiksi kuten alareunan widgetissa"* + *"vois olla samanlainen kun
   se alareunan widgetti muutenkin"* + *"katohan etta on samat tyylit kun
   alareunan boksissa"*. Aiemmin nama olivat `.painike.maksullinen`-jattinappeja
   pystyparina lahderivin kanssa; Teemu: *"aika massiivinen tuossa ja jotenkin
   hankalan nakoinen"*.

   ⚠️ ASETTELU EI OLE TASSA — se tulee JAETUISTA `.medialista` +
   `.medialista.kompakti` -luokista, jotka komponentti asettaa samaan <ul>:aan
   (`class="medialista kompakti koosta-kategoriat"`). Ne antavat jo flexin,
   flex-wrapin, gapin, paddingin, marginin, `list-style: none`:n JA
   `.medialista li`:n kautta <li>:n merkin vaimennuksen. Linkin typografia
   tulee jaetusta `.kompakti-linkki`-luokasta (ks. sen lohko ylempana).

   ALA TOISTA NAITA DEKLARAATIOITA TASSA (#1130, 31.8.2026). Aiemmin
   `.koosta-kategoriat` deklaroi ne itse, ja koska kopio eli erillaan
   alkuperaisesta, se ehti ajautua erilleen: #1129:n asettelumuutos poisti
   <li>:n oman saannon (`display:flex` pystyparille), eika kukaan huomannut
   etta samalla katosi ainoa paikka jossa <li>:n listamerkki oli vaimennettu
   — pico.classless `ul li{list-style:square}` osuu SUORAAN listaelementtiin,
   joten pelkka `ul`-tason `list-style: none` ei sita estanyt. Tulos:
   nelioluodit nakyivat tuotannossa. Sama juurisyy oli jo osunut kahdesti
   (palaute #120 ja #221) ja se oli kirjattu tahan tiedostoon kommenttina —
   dokumentaatio ei siis riittanyt. Nyt asettelu on YHDESSA paikassa, joten
   erilleen ajautuminen ei ole enaa mahdollista, ja `--group=frontend`:in
   "ul/li list-style -pari" -vahti (test-css-style.sh [6/6], konfiguraatio
   test/css-velka-merkit.json § ul_li_list_style) failaa jos joku palauttaa
   ul-tason `list-style: none`:n ilman li-parin. Vahti lisattiin 31.8.2026;
   tama kommentti nimesi sen aiemmin omaksi skriptikseen jota ei ollut.

   Lahderivi ("Mediat: ...") POISTETTU #1129:ssa Teemun paatoksella —
   kattavuus kerrotaan nyt tulosnakymassa (`koostehaku_lahteiden_tila`).
   Siksi <li> ei ole enaa pystypari eika tarvitse omaa flex-suuntaa.

   Ei omaa font-sizea: koko tulee `.kompakti-linkki`:lta, joten app.css:n
   font-size-laskuri ei kasva (kynnys 232, nykytila 227). */
/* Auki oleva kategoria. EI outlinea: se piirtyi napin ympari laatikkona ja
   nayttaisi tekstilinkilla irralliselta kehykselta. Tekstille sopiva
   korostus = taysi tekstivari + lihavointi, eli sama suunta kuin
   `.kompakti-linkki:hover` mutta pysyvana. Ei omaa font-sizea (ks. ylla).
   Merkinta on tarkoituksella KAKSIKANAVAINEN: `aria-current="page"` kertoo
   saman ruudunlukijalle, joten korostus ei ole ainoa signaali. */
.koosta-kategoriat .valittu {
  color: var(--color-text);
  font-weight: 600;
}

/* Konteksti: ylapalkki. Mobiilissa piilotettu, koska mobiilivalikossa on oma
   kutsu — nakyvia hakukenttia on aina tasan yksi. */
.ylapalkki .hakukentta {
  display: none;
}
.ylapalkki .mobiilivalikko-haku {
  padding: var(--space-2) 0;
}
.ylapalkki .mobiilivalikko-haku .hakukentta {
  display: flex;
}
/* Sama kynnys kuin `.nav-linkit`in naytolla (1024 px, #1422) — ks. ylapalkki. */
@media (min-width: 1024px) {
  .ylapalkki .hakukentta {
    display: flex;
    width: auto;
    flex: 0 1 14rem;
    margin-left: var(--space-2);
  }
}

/* --- hakutulos-ryhma (domain-komponentti, #706) ---
   Yhden osumatyypin lohko hakutulossivulla (/haku): otsikko + linkkilista.
   Ei korttikehysta — tama on paarungon tulosryhma, ei sivupalkin kortti
   (ks. spec/komponentit.md perustelu vs. sivupalkki-kortti). */
.hakutulos-ryhma {
  margin-top: var(--space-5);
}
.hakutulos-ryhma h2 .maara {
  color: var(--color-muted);
  font-family: var(--font-label);
  font-weight: 400;
}
.hakutulos-ryhma ul {
  list-style: none;
  padding: 0;
  margin: 0;
}
.hakutulos-ryhma li {
  padding: var(--space-2) 0;
  /* list-style PITAA asettaa li:lle, ei pelkalle ul:lle (palaute #221 — sama
     juurisyy kuin .hakutulos-ehdotukset li:ssa alla, palaute #120):
     pico.classless `ul li{list-style:square}` kohdistuu suoraan
     listaelementtiin, joten ylla oleva `.hakutulos-ryhma ul{list-style:none}`
     ei estä merkin piirtoa. Sama korjaus kuin .medialista li ja .tag-list li. */
  list-style: none;
}
.hakutulos-ryhma .hakutulos-alias {
  color: var(--color-muted);
  margin-left: var(--space-2);
}

/* Hakusivun ehdotuslohko (tyhja tulos / ei viela hakusanaa). */
.hakutulos-ehdotukset {
  margin-top: var(--space-5);
}
.hakutulos-ehdotukset ul {
  list-style: none;
  padding: 0;
  margin: 0;
}
.hakutulos-ehdotukset li {
  padding: var(--space-1) 0;
  /* list-style PITAA asettaa li:lle, ei pelkalle ul:lle (palaute #120):
     pico.classless sisaltaa `ul li{list-style:square}` joka kohdistuu suoraan
     listaelementtiin. Ylla oleva `.hakutulos-ehdotukset ul{list-style:none}`
     voittaa spesifisyydella UL:n tasolla, mutta merkin piirtaa LI — ja sen
     oma saanto voittaa perityn arvon. Siksi DevTools nayttaa UL:lle "none"
     samalla kun kayttaja nakee nelioluodit. Sama korjaus kuin .medialista li
     ja .tag-list li. */
  list-style: none;
}

/* --- tags ---
   Peruslista + inline-variantti. */
ul.tags { display: flex; flex-wrap: wrap; gap: var(--space-1); list-style: none; padding: 0; }
ul.tags li { list-style: none; background: var(--color-surface); padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); }
ul.tags.inline li { display: inline; background: none; padding: 0; }

/* --- merkki (badge) ---
   Varipohjaiset variantit. PHP-helper paattaa varin, CSS ei tieda kayttotarkoitusta.
   Perusluokka + varivariantti (esim. .merkki.punainen).
   Aiemmin: .badge, .badge-bias-*, .badge-article-*, .badge-political-* jne. */
.merkki {
  display: inline-flex;
  align-items: center;
  padding: 0.15em 0.6em;
  border-radius: var(--radius-full);
  font-family: var(--font-label);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
  background: var(--color-surface);
  color: var(--color-muted);
}
.merkki.punainen   { background: var(--color-bias-high-bg);    color: var(--color-bias-high-text); }
.merkki.keltainen  { background: var(--color-bias-medium-bg);  color: var(--color-bias-medium-text); }
.merkki.vihrea     { background: var(--color-bias-low-bg);     color: var(--color-bias-low-text); }
.merkki.harmaa     { background: var(--color-bias-neutral-bg); color: var(--color-bias-neutral-text); }
.merkki.violetti   { background: var(--color-violetti-bg);     color: var(--color-violetti-text); }
.merkki.sininen    { background: var(--color-primary-bg);      color: var(--color-sininen-text); }
.merkki.tumma      { background: var(--color-primary);    color: var(--color-text-inverse); }
.merkki.tumma:hover { background: var(--color-primary-light); color: var(--color-text-inverse); }
.merkki.kritisoitu { background: var(--color-kritisoitu-bg);   color: var(--color-kritisoitu-text); }
.merkki.puolustettu { background: var(--color-puolustettu-bg); color: var(--color-puolustettu-text); }

/* --- modaali ---
   Geneerinen overlay-dialogi. Glassmorphism sisaltolaatikolle.
   Alpine.js ohjaa x-show:lla, transitiot x-transition:lla. */

.modaali {
  display: flex;
  position: fixed;
  inset: 0;
  z-index: 200;
  align-items: center;
  justify-content: center;
  padding: var(--space-3);
}
.modaali .tausta {
  position: absolute;
  inset: 0;
  background: var(--color-overlay);
  /* CSS-transitiot: Alpine x-show togglee display, CSS hoitaa faden. */
  transition: opacity 150ms ease;
}
.modaali .sisalto {
  position: relative;
  width: 100%;
  max-width: 480px;
  max-height: 90vh;
  overflow-y: auto;
  /* Vieritys ei valu taustalle kun sisalto on vieritetty reunaan (#1084).
     Body-lukko estaa taustan vierityksen, tama estaa ketjutuksen. */
  overscroll-behavior: contain;
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-float);
  padding: var(--space-4);
  /* CSS-transitiot: Alpine x-show togglee display, CSS hoitaa faden. */
  transition: opacity 150ms ease, transform 150ms ease;
}
.modaali .sulje {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--color-muted);
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: color var(--transition-fast), background var(--transition-fast);
}
.modaali .sulje:hover {
  color: var(--color-text);
  background: var(--color-surface);
}

/* Size-variantit */
.modaali.sm .sisalto { max-width: 360px; }
.modaali.lg .sisalto { max-width: 640px; }

/* Body scroll lock (#1084). Luokan asettaa modaali.php:n x-effect, joka kutsuu
   modaaliScrollLukko():ta (app.js) — ei mikaan kutsupaikka. Lukko on
   laskurillinen: layoutissa on nelja modaalia yhta aikaa DOM:issa, ja luokka
   pysyy niin kauan kuin yksikin niista on auki. Vierityspalkin katoamisen
   aiheuttaman sivusuuntaisen hypyn kompensoi JS asettamalla bodylle inline
   padding-right — sita EI voi tehda taalla, koska palkin leveys on mitattava
   ajossa. Ennen #1084 tassa oli kommentti joka kuvasi tata toteutusta
   indikatiivissa, vaikka mikaan ei asettanut luokkaa. */
body.modaali-auki { overflow: hidden; }
/* iOS-Safarissa vierittava elementti on documentElement, joten pelkka
   body-saanto ei pida taustaa paikallaan. Selaimessa jossa :has() ei toimi
   tama rivi ohitetaan ja body-saanto jaa voimaan — progressiivinen parannus,
   ei riippuvuus.

   HUOM: tama on app.css:n ENSIMMAINEN oma :has()-valitsin. Muut osumat
   tiedostossa (r. ~1127, ~1148, ~1335) ovat kommenttitekstia: kaksi ensimmaista
   lainaavat PICON :has()-saantoa jota .hakukentta nimenomaan neutraloi, ja
   kolmas kertoo etta .lahde-valinta rakennettiin tulemaan toimeen ILMAN
   :has():ia. Ala siis lue niita ennakkotapauksena — ennakkotapaus on tama rivi. */
html:has(> body.modaali-auki) { overflow: hidden; }

/* Desktop */
@media (min-width: 768px) {
  .modaali .sisalto { padding: var(--space-5); }
}

/* --- kirjautumis-modaali ---
   Domain-komponentti joka wrappaa component('modaali').
   Alpine-ohjattu sahkopostikirjautuminen (magic link).
   Lisatty task #248:ssa deprekoitujen utility-luokkien siirron yhteydessa. */

.kirjautumis-modaali p { font-size: 0.875rem; color: var(--color-muted); }
.kirjautumis-modaali .vaihda-tila { text-align: center; }

/* Erotin Google-napin ja sahkopostilomakkeen valilla (#1379).
   Hyphenated lohko-elementti EIKA `.kirjautumis-modaali .erotin`, koska
   `.erotin` on jo varattu globaalisti kortin meta-erottimelle (osio 6) —
   frontend-css.md § Nimeamiskonventiot, sama kuvio kuin .koosta-sivutus-erotin.
   Viiva piirretaan reunuksilla eika pseudo-elementeilla, jotta teksti
   keskittyy flexilla ilman absoluuttista asemointia. */
.kirjautumis-modaali-erotin {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-3) 0;
  font-size: 0.8125rem;
  color: var(--color-muted);
}
.kirjautumis-modaali-erotin::before,
.kirjautumis-modaali-erotin::after {
  content: "";
  flex: 1;
  border-top: 1px solid var(--color-border);
}

/* Lahetetty-tilan osoite + napit (#1379, umpikujan purku).
   Osoite naytetaan koska kayttaja ei muuten nae mihin linkki lahti — juuri se
   puute lukitsi Teemun selaimen autofillin ehdottamaan vaaraan osoitteeseen
   16.9.2026. Kumoaa `.kirjautumis-modaali p` -muted-saannon: tama rivi on
   tarkistettava tieto, ei selite. `break-word` koska pitka osoite ei saa
   levittaa modaalia (sm-koko, 360 px -mobiili).
   Napit ovat flex-rivi eivatka utility-luokkia: `.flex-wrap` asettaa vain
   flex-wrapin, `display:flex`-utilitya ei ole whitelistilla (frontend-css.md
   § Sallitut utility-luokat) -> asettelu kuuluu komponenttiin. */
/* Scopettu tarkoituksella: `.kirjautumis-modaali p` on (0,1,1) ja maalaa
   kaikki modaalin kappaleet mutediksi, joten paljas (0,1,0) -luokka havisi
   sille. Tama on (0,2,0) = voittaa luokkamaaralla. */
.kirjautumis-modaali .kirjautumis-modaali-osoite {
  font-family: var(--font-label);
  font-size: 0.9375rem;
  color: var(--color-text);
  /* `anywhere` EIKA `break-word` (#1344 saanto 2): tama rivi kantaa kayttajan
     kirjoittamaa sahkopostiosoitetta eli mielivaltaista dataa, jossa ei ole
     rivinvaihtokohtia. Vain `anywhere` pienentaa myos min-content-leveytta;
     `break-word` jattaa lukon paikalleen ja 60+ merkin osoite levittaisi
     modaalin yli 360 px:n nakyman. Sama valinta kuin .merkki.aihe:ssa. */
  max-width: 100%;
  overflow-wrap: anywhere;
}
.kirjautumis-modaali-napit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* --- krediitti-modaali ---
   Domain-komponentti kirjautuneen kayttajan krediittien ostoon.
   Lisatty task #248:ssa. Ei omia saantoja: selitetekstit tulevat
   .info-teksti-perussaannosta (osio 6, #790). */

/* --- dialogi-kortti ---
   Native HTML dialog korttina (kopioi prompti, ilmoita virhe, analysoi
   uudelleen). Avataan showModal():lla -> top layer + ::backdrop.

   LUOKKARAJATTU TARKOITUKSELLA (#1269). Paljas `dialog` on Picossa OVERLAY,
   ei kortti: `display:flex; position:fixed; inset:0; min-width:100%;
   min-height:100%; background:<overlay>` ja kortti on `dialog > article`.
   `profile.php`:n tilinpoisto kayttaa juuri sita Picon kuviota, joten
   korttityylia EI saa asettaa paljaalle `dialog`-elementille — muuten sen
   overlaysta tulisi 420 px:n laatikko jonka sisalla on toinen kortti.

   Kolme Picon oletusta on kumottava, ja vain yksi niista on
   spesifisyyskysymys:
     1. `min-width`/`min-height` voittavat `max-width`/`max-height` AINA,
        riippumatta spesifisyydesta ja lahdejarjestyksesta (CSS 2.1 10.4).
        Ilman eksplisiittista `min-*: 0` kortti levittautui koko nakymaan —
        mitattu 1585x773 px valkoisena paneelina.
     2. `background` on Picolla puolilapinakyva overlay-vari; kortti tarvitsee
        oman lapinakymattoman taustansa (ja saa himmennyksen ::backdropista).
     3. `display: flex` -> kortti asemoi itsensa, ks. `[open]`-saanto alla.

   Keskitys: `inset: 0` + `margin: auto` + `height: fit-content` on selaimen
   OMA dialog-keskityskuvio (HTML-speksin UA-tyyliarkki). `height: auto` EI
   keskita pystysuunnassa vaan venyttaa korkeuden taydeksi — CSS 2.1 10.6.4
   nollaa auto-marginaalit kun korkeus on auto.

   `vh` eika `dvh`: `dvh`:ta ei kayteta muualla app.css:ssa, eika uutta
   yksikkoa tuoda tassa taskissa. */

dialog.dialogi-kortti {
    position: fixed;
    inset: 0;
    margin: auto;
    width: 90%;
    min-width: 0;
    max-width: 420px;
    height: fit-content;
    min-height: 0;
    max-height: calc(100vh - var(--space-5));
    overflow: auto;
    border: none;
    box-shadow: var(--shadow-float);
    padding: var(--space-5);
    background: var(--color-bg);
}
/* Vain [open]-haarassa: Picon `dialog:not([open]){display:none}` on (0,2,0) ja
   `dialog.dialogi-kortti` olisi sama (0,2,0) mutta myohemmin lahteessa —
   `display` perussaannossa tekisi suljetusta dialogista nakyvan. */
dialog.dialogi-kortti[open] {
    display: block;
}
dialog::backdrop {
    background: var(--color-overlay);
}
.dialog-inner {
    max-width: 420px;
    margin: 0 auto;
}
.dialog-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
    margin-top: var(--space-5);
}

/* --- dialogi-vaihe ---
   Numeroitu vaihe kaksivaiheisessa dialogissa ("Tarkista itse", task #750).
   Erotin taustavarilla, ei 1px-viivalla (No-Line Rule). */
.dialogi-vaihe {
    background: var(--color-surface-low);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    margin-bottom: var(--space-3);
}
.dialogi-vaihe h4 {
    margin: 0 0 var(--space-1);
    font-family: var(--font-heading);
    font-size: 1rem;
}
.dialogi-vaihe p {
    font-size: 0.875rem;
    color: var(--color-muted);
}
.dialogi-vaihe .prompt-textarea {
    margin-bottom: var(--space-2);
}

/* --- prompt-textarea --- */
.prompt-textarea {
    width: 100%;
    font-size: 0.8rem;
    font-family: monospace;
}

/* --- report-fieldset --- */
.report-fieldset {
    border: none;
    padding: 0;
    margin: 0 0 var(--space-3) 0;
    display: flex;
    flex-direction: column;
}
.report-fieldset label {
    display: block;
    margin-bottom: var(--space-2);
}

/* --- artikkelikortti ---
   Domain-komponentti: yksittaisen artikkelin kortti.
   Navy accent-stripe vasemmassa reunassa, korttipohjainen layout. */

.artikkelikortti {
  display: block;
  width: 100%;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-left: var(--accent-stripe) solid var(--color-primary);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  transition: box-shadow var(--transition-base);
}
.artikkelikortti:hover {
  box-shadow: var(--shadow-sm);
}
.artikkelikortti.compact {
  padding: var(--space-2);
}
.artikkelikortti h3 {
  font-size: 1rem;
  margin: 0 0 var(--space-2);
  line-height: var(--line-height-tight);
}
.artikkelikortti h3 a {
  color: var(--color-text);
  text-decoration: none;
}
.artikkelikortti h3 a:hover {
  color: var(--color-primary-light);
}
/* Otsikkoprefix listoissa: "Analyysi artikkelista:" pienempana ja
   muted-varilla omalla rivillaan ennen alkuperaista otsikkoa. Sama
   <a>:n sisalla -> klikki vie analyysiin. */
.artikkelikortti .otsikko-prefix {
  display: block;
  font-size: .75rem;
  font-weight: 400;
  color: var(--color-muted);
  font-family: var(--font-label);
  letter-spacing: 0;
  margin-bottom: var(--space-1);
  text-transform: none;
}
.artikkelikortti .otsikko-paa {
  display: block;
}
.artikkelikortti .meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-1);
  font-size: .75rem;
  color: var(--color-muted);
}
.artikkelikortti .meta a { text-decoration: none; } /* meta-linkkien puhdas tyyli (korvaa .link-plain) */
.artikkelikortti .erotin {
  color: var(--color-border);
  font-size: .75rem;
}

/* Koostesivun ingressi otsikon alle (task #394, MediaTitleParser).
   Sub-style: pienempi, muted-vari, line-clamp 2 riviin. */
.artikkelikortti-ingressi {
  font-size: .875rem;
  color: var(--color-muted);
  margin: var(--space-1) 0 var(--space-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Koostesivun premium-varoitus kortin alaosaan (task #394).
   Pieni teksti, muted-vari. */
.artikkelikortti-varoitus {
  display: block;
  margin-top: var(--space-1);
  font-size: .75rem;
}

/* Bias-badge-varit siirtyivat paavalikoimaan .merkki.punainen/keltainen/vihrea
   (ks. merkki-lohko ~rivi 555). Poistettu 20.4.2026 (task 192). */

/* Desktop */
@media (min-width: 768px) {
  .artikkelikortti { padding: var(--space-4); }
  .artikkelikortti.compact { padding: var(--space-2) var(--space-3); }
}

/* --- artikkelilista ---
   Wrapperi artikkelikorteille. Flex-column + gap. */

.artikkelilista {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* --- koostetulokset (#1352, palaute #345)
   Koostelistan sailio + "Piilota jo analysoidut" -kytkin. Kayttaja etsii
   koosteelta sita mita han EI ole viela analysoinut.

   ⚠️ MIKSI SAILION TILALUOKKA EIKA per-kortti-tila: kytkin vaihtaa yhden
   luokan yhdessa elementissa, ei N korttia. Sama syy miksi suodatus on
   CSS:ssa eika palvelimella: fragmentin uudelleenhaku VELOITTAA krediitin
   (Views/partials/koostehaku-tulokset.php), joten listan katselutapa ei saa
   maksaa rahaa.

   ⚠️ EI `[hidden]`-attribuuttia (oppi #912/#945): `[hidden]` on selaimen
   oletussaanto ja kumoutuu hiljaa heti kun jokin luokka asettaa display'n —
   ja `.artikkelilista { display: flex }` on tasan sen kortin esivanhempi.
   Eksplisiittinen `display: none` tekijan saantona ei voi hukkua.

   ⚠️ SAANTO ON RAJATTU SAILION ALLE TARKOITUKSELLA. `.artikkelikortti` on
   projektin laajimmin kaytetty komponentti; `.artikkelikortti.analysoitu`
   ilman `.koostetulokset`-etuliitetta osuisi jokaiseen listaan sivustolla.
   Tilaluokka kertoo tilan, sailio paattaa mita tilalla tehdaan.

   TASSA ON TASAN YKSI SAANTO. Kytkinlabel kayttaa olemassa olevia `.info-teksti`
   (typografia) ja `.mb-md` (vali) -luokkia — `<label>` on jo Picolta
   `display: block`, joten oma luokka olisi kolme deklaraatiota jotka ovat
   muualla valmiina (§ Jaettu perusluokka). Sailio itse on layout-neutraali
   kaare: sille EI kirjoiteta saantoa, muuten se muuttaa listan ulkoasua. */

.koostetulokset.piilota-analysoidut .artikkelikortti.analysoitu {
  display: none;
}

/* --- ranking-lista (#432 Vaihe 6 — /parhaat-viikko)
   Numeroitu top-N lista artikkelikorteille. <ol> pidetaan saavutettavuuden
   takia (semantiikka: jarjestetty lista). Selain piilotetaan oletusnumerointi
   ja .ranking-numero rendaa numeron visuaalisesti. */

.ranking-lista {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  counter-reset: ranking;
}
.ranking-rivi {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}
.ranking-rivi > .artikkelikortti {
  flex: 1;
  min-width: 0;
}
.ranking-numero {
  flex: 0 0 auto;
  min-width: 2ch;
  font-weight: 700;
  color: var(--color-muted);
  padding-top: var(--space-1);
  text-align: right;
}

/* --- parhaat-viikko-sivu (#432 Vaihe 6) */
.parhaat-viikko-footer {
  margin-top: var(--space-4);
  text-align: center;
}

/* --- discover-sivu ---
   Koostesivun omat tyylit. */

.discover-lahde {
  color: var(--color-muted);
  font-size: .875rem;
}
/* Tuoreusleima yli tuoreusrajan (#792). Vain vari muuttuu — ei uutta
   font-sizea (font_size_max-budjetti, test/css-velka-merkit.json). Vari ei ole
   ainoa signaali: leima saa myos oman lauseensa (tuoreus_vanhentunut). */
.discover-sivu .info-teksti.vanhentunut {
  color: var(--color-warning);
}
.discover-takaisin {
  margin-top: var(--space-4);
}

/* Ankkurihyppy /koosta:n osoitelohkoon (#964, palaute #224). Koostehaun
   tulosten alanosto linkittaa /koosta#koosta-osoitteesta:aan, ja .ylapalkki on
   sticky top:0 → se peittaisi lohkon ylareunan eli juuri sen otsikon joka
   vastaa linkin tekstia. Sama jo kaytossa oleva ratkaisu kuin .hero:ssa
   (r. ~724, #652): --ylapalkki-korkeus asetetaan ajossa ylapalkin todellisesta
   korkeudesta (app.js), fallback 52px matchaa sita. EI uutta tokenia eika
   px-arvoa — vain sama laskukaava kuin naapureilla.

   SAANTO KOHDISTUU LUOKKAAN VAIKKA ANKKURI ON ID. Ne ovat samalla elementilla
   (`<section id="koosta-osoitteesta" class="koosta-osoite-nosto">`), joten
   kohde on sama kummin pain tahansa — mutta ID-valitsin olisi spesifisyydeltaan
   (1,0,0) ja jaisi tiedoston ainoaksi sellaiseksi. Tama tiedosto on kaynyt
   spesifisyyskisansa luokkatasolla (ks. frontend-css.md § Picon lomakesaannot
   ja `.hakukentta.hakukentta` -kaksinnus r. ~1229), ja yksittainen ID-poikkeus
   olisi juuri se rivi jonka seuraava lukija joko kopioi tai joutuu
   selittamaan. `.hero`n ennakkotapaus (r. ~724) on niin ikaan luokka. */
.koosta-osoite-nosto {
  scroll-margin-top: calc(var(--ylapalkki-korkeus, 52px) + var(--space-3));
}

/* --- koosta-historia ---
   Aiempien koostesivu-vierailujen lista (/koosta ilman url-paramia).
   Task #424. */

.koosta-historia-lista {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* Pico-luodin vaimennus <li>-tasolla (vahti: --group=frontend). */
.koosta-historia-lista li { list-style: none; }
.koosta-historia-rivi {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.koosta-historia-rivi:hover {
  border-color: var(--color-primary);
}
.koosta-historia-linkki {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  text-decoration: none;
  flex: 1;
  min-width: 0;
}
.koosta-historia-media {
  font-weight: 600;
}
.koosta-historia-osio {
  font-size: .875rem;
  color: var(--color-muted);
}
.koosta-historia-meta {
  white-space: nowrap;
}
.koosta-historia-tyhja {
  padding: var(--space-4);
  text-align: center;
}
.koosta-historia-esimerkit {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: center;
}
/* Pico-luodin vaimennus <li>-tasolla (vahti: --group=frontend). <li> on paljas
   (ei omaa luokkaa eika display-muutosta), joten luoti oli TAALLA nakyva. */
.koosta-historia-esimerkit li { list-style: none; }

/* --- koosta-sivutus ---
   Koostesivun sivutuskontrollit (task #474). Edellinen/numerot/Seuraava-nappi-rivi.
   Renderoidaan vain kun extraktori palauttaa pagination-datan. */

.koosta-sivutus {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: center;
  align-items: center;
  margin: var(--space-4) 0 var(--space-3);
}
.koosta-sivutus-nappi {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  text-decoration: none;
  font-size: .9rem;
  min-width: 2rem;
  justify-content: center;
}
.koosta-sivutus-nappi:hover {
  border-color: var(--color-primary);
}
.koosta-sivutus-nappi.aktiivinen {
  background: var(--color-primary);
  color: var(--color-bg);
  border-color: var(--color-primary);
  font-weight: 600;
}
.koosta-sivutus-nappi-disabled {
  opacity: .4;
  cursor: not-allowed;
}
.koosta-sivutus-erotin {
  color: var(--color-muted);
  padding: 0 var(--space-1);
}

/* --- palvelu-osio ---
   Osiovälitys palvelu- ja tilannekatsaussivuilla. */

.palvelu-osio {
  padding: var(--space-5) 0;
}
.palvelu-osio + .palvelu-osio {
  border-top: 1px solid var(--color-border);
}
.palvelu-lead {
  max-width: 640px;
  margin: var(--space-2) auto var(--space-4);
  font-size: 1.05rem;
}
.palvelu-cta {
  text-align: center;
  padding-bottom: var(--space-6);
}

/* --- korttiruudukko ---
   Grid-layout korteille (3 saraketta, mobiilissa 1). */

.korttiruudukko {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--space-3);
}
.korttiruudukko.kolme {
  grid-template-columns: 1fr;
}
/* Kahden kortin ruudukko (#652). Oletus-auto-fill tekisi 1400px:n containerissa
   neljä saraketta, jolloin kaksi korttia jäisi kapeina vasempaan reunaan. */
.korttiruudukko.kaksi {
  grid-template-columns: 1fr;
}
@media (min-width: 769px) {
  .korttiruudukko.kolme { grid-template-columns: repeat(3, 1fr); }
  .korttiruudukko.kaksi { grid-template-columns: repeat(2, 1fr); }
}
/* Lohkoruudukko (#1440, palaute #267): ISOT lohkot (tunneprofiili-kortit)
   leveysasteikolla. auto-fill + --lohko-kapea -> 1400px:ssa kaksi puolikasta,
   kapealla yksi sarake; lohkot "ryhmittyvat itsestaan" ilman breakpointia.
   auto-fill eika auto-fit: yksinainen lohko pysyy sarakkeen levyisena eika
   veny takaisin koko leveyteen. min(100%, ...) estaa vaakaylivuodon alle
   480px:n ruuduilla (§ Vaakaylivuoto). */
.korttiruudukko.lohkot {
  grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--lohko-kapea)), 1fr));
  gap: var(--space-4);
  align-items: start;
}

/* --- jakaumapalkit ---
   Poliittisen kehystyksen progress-bar -komponentti. */

.jakaumapalkit {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-width: 720px;
  margin: 0 auto;
}
.jakaumapalkki .nimi {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.95rem;
  margin-bottom: var(--space-1);
}
.jakaumapalkki .osuus {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--color-muted);
}
.jakaumapalkki .raita {
  position: relative;
  width: 100%;
  height: 10px;
  background: var(--color-surface);
  border-radius: 5px;
  overflow: hidden;
}
.jakaumapalkki .tayte {
  height: 100%;
  background: var(--color-primary);
  border-radius: 5px;
  transition: width 0.3s ease;
}

/* --- aiemmat-paivat ---
   Historiallisten päivien linkkilista. */

.aiemmat-paivat {
  list-style: none;
  padding: 0;
  max-width: 480px;
  margin: 0 auto;
}
.aiemmat-paivat li {
  list-style: none;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.aiemmat-paivat li:last-child {
  border-bottom: none;
}
.aiemmat-paivat a {
  display: block;
  text-decoration: none;
}

/* --- ilmoitus ---
   Flash-viestit (success, warning, danger). */

.ilmoitus {
  padding: var(--space-3);
  border-radius: var(--radius-md);
  font-size: .875rem;
}
.ilmoitus.success {
  background: var(--color-bias-low-bg);
  color: var(--color-bias-low-text);
}
.ilmoitus.info {
  background: var(--color-primary-subtle);
  border-left: 4px solid var(--color-primary);
  color: var(--color-text);
}

/* Virhe-incident_id (tukikoodi) — pieni harmaa rivi virhealueessa.
   Lähde: spec/backend-virheenkasittely.md (task #329 vaihe 1). */
.virhe-tukikoodi {
  display: block;
  margin-top: var(--space-1);
  color: var(--color-muted);
  font-size: .8125rem;
  font-family: monospace;
}

/* Tuntematon media -ehdotus: typo-suggestion-rivi (#400, 9.5.2026).
   Naytetaan ilmoitus-laatikon (analyze.php) tai .ohjaus-laatikon (alapalkki)
   sisalla "Tarkoititko: hs.fi, yle.fi?" -muodossa. Pieni rivi, hillitty väri,
   linkit alleviivattuna jotta klikattavuus ilmenee. No-BEM: ei __ehdotus,
   suoraan .media-typo-ehdotus joka on yksiselitteinen domain-luokka. */
.media-typo-ehdotus {
  font-size: .8125rem;
  color: var(--color-muted);
  margin-bottom: var(--space-2);
}
.media-typo-ehdotus a { text-decoration: underline; }

/* --- haitari (kehys-komponentti) ---
   Numeroitu kollapsoituva paneeli teaser-rivilla. Pohjapalikka single-uudistuksen
   syvaanalyysiosiolle, yleiskayttoinen. Lahde: spec/komponentit.md, task #439. */
.haitari {
  margin-bottom: var(--space-3);
  background: var(--color-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}
.haitari > summary {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-3);
  cursor: pointer;
  list-style: none;
  font-family: var(--font-body);
  border-radius: var(--radius-md);
  transition: background var(--transition-fast);
}
.haitari > summary::-webkit-details-marker { display: none; }
.haitari > summary::after { display: none; } /* Pico disclosure-kolmio pois — oma .haitari-nuoli hoitaa */
.haitari > summary:hover { background: var(--color-surface-high); }
.haitari > summary:focus-visible {
  outline: 2px solid var(--color-outline);
  outline-offset: 2px;
}
.haitari-numero {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.75rem;
  height: 1.75rem;
  padding: 0 var(--space-1);
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-text-inverse);
  font-family: var(--font-label);
  font-weight: 600;
  flex-shrink: 0;
}
.haitari-otsikko {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  color: var(--color-text);
}
.haitari-teaser {
  /* Mobile-first: teaser omalle taysleveälle rivilleen. Ilman tata
     .haitari-otsikko { flex: 1 } puristaa teaserin pystyyn kapealla naytolla
     (yksi merkki per rivi). order: 1 + flex-basis: 100% wrappaa teaserin
     uudelle riville; numero + otsikko + nuoli jaavat ensimmaiselle riville
     (nuoli oikeaan reunaan otsikon flex: 1 -tyontamana). */
  order: 1;
  flex-basis: 100%;
  color: var(--color-muted);
  font-family: var(--font-label);
}
.haitari-nuoli {
  color: var(--color-muted);
  transition: transform var(--transition-base);
  flex-shrink: 0;
}
.haitari[open] > summary .haitari-nuoli { transform: rotate(180deg); }

/* Desktop: teaser takaisin samalle riville (kuten ennen). */
@media (min-width: 768px) {
  .haitari-teaser {
    order: 0;
    flex-basis: auto;
  }
}
.haitari-sisalto {
  padding: var(--space-3);
  border-top: 1px solid var(--color-border);
  line-height: var(--line-height-base);
}

/* Haitari tiiviin kortin sisalla (task #878, palaute #184: asiantuntijakortin
   lahdelista). Kortti on jo oma pintansa ja padding-tasonsa — haitarin oma
   tausta + varjo + space-3-padding tekisi siita laatikon laatikossa. */
.kortti.compact .haitari {
  background: transparent;
  box-shadow: none;
  margin-bottom: var(--space-2);
}
.kortti.compact .haitari > summary { padding: var(--space-1) 0; }
.kortti.compact .haitari-sisalto { padding: var(--space-2) 0 0; }
/* Teaser kiinni otsikkoon tiiviissa kortissa. Perussaannon `.haitari-otsikko
   { flex: 1 }` on tehty leveille syvaanalyysipaneeleille joissa teaser on oma
   lause ("3 tekniikkaa, 2 vetoamista") ja kuuluu oikeaan reunaan. Tassa teaser
   on pelkka lukumaara ("Lahteet: 12"), joten reunaan tyonnettyna se irtoaa
   otsikostaan — ja mobiilissa perussaannon `flex-basis: 100%` veisi paljaan
   numeron kokonaan omalle rivilleen. Nuoli pysyy oikeassa reunassa
   `margin-right: auto`n ansiosta. Mitattu tuotannosta #878:ssa. */
.kortti.compact .haitari-otsikko { flex: 0 0 auto; }
.kortti.compact .haitari-teaser {
  order: 0;
  flex-basis: auto;
  margin-right: auto;
  padding-left: var(--space-1);
}

/* --- syvaanalyysi-otsikko ---
   Vinouma-tabin Tason 1 (paahavainnot) ja Tason 2 (numeroidut haitarit)
   valinen erotin. Task #440, 23.5.2026 — alustava sijoitus .valilehti-divin
   sisalla. Task #441 siirtaa Tason 2 (haitarit) full-width-laatikkoon
   tabin alapuolelle; silloin .syvaanalyysi-otsikko jaa Taso 2 -laatikon
   alkuun. */
.syvaanalyysi-otsikko {
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  font-size: 1rem;
  color: var(--color-muted);
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* --- merkki (etusivun trendit) ---
   Tagi-napit trending-osiolle. */

.merkki.aihe {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  font-size: .8125rem;
  font-family: var(--font-label);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
  /* `.merkki`-perusluokka on `white-space: nowrap` (rivi ~1704), mika on oikein lyhyille
     badgeille — mutta `.aihe`-variantti kantaa mielivaltaisen pituista aiheen nimea
     kannasta, eika nowrap-merkki voi kutistua min-contentin alle. Mitattu /aiheet
     11.9.2026: 345 px 328 px:n kontissa (+0.9 px yli 360 px:n nakyman) → tama tuottaa
     dokumentin vaakaylivuodon joka siirtaa kelluvan .analyysijonon ruudun ulkopuolelle
     (sama ketju kuin #1271). Korjattuna 328 px, merkki rivittyy kahdelle riville.
     Perusluokkaa EI muuteta: `.merkki{max-width:100%;overflow-wrap:anywhere}` mitattiin
     eika se muuttanut leveytta lainkaan — vain `white-space: normal` purkaa lukon. */
  max-width: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
}
.merkki.aihe:hover,
.merkki.aihe.aktiivinen {
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border-color: var(--color-primary);
}
.merkki .lkm {
  font-size: .6875rem;
  opacity: 0.7;
}

/* --- tag-list (aiheet-arkistosivu /aiheet, #403) ---
   Aakkosjarjestyksessa ryhmitellyt aihetagit. Reusaa .merkki.aihe -tagimerkit;
   wrapperi hoitaa flex-layoutin ja aakkos-otsikon koko-rivi-katkon. */

.tag-list li { list-style: none; }
.tag-list.alphabetical {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.tag-list.alphabetical li { margin: 0; }
.tag-list.alphabetical .aakkos-otsikko-rivi {
  /* Otsikkorivi kayttaa koko leveyden -- katkaisee flexin uudelle riville */
  flex-basis: 100%;
}
.tag-list.alphabetical .aakkos-otsikko {
  margin: var(--space-4) 0 var(--space-2) 0;
  font-size: 1.25rem;
  scroll-margin-top: var(--space-5); /* sticky-headerin alle scrollatessa */
}
/* plain-variantti (#611): ei-linkittava pillilista (lahteen asiasanat).
   Sama flex-layout kuin alphabetical, ilman aakkos-otsikkorakennetta. */
.tag-list.plain {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.tag-list.plain li { margin: 0; }
/* active-filters-variantti (#676, tiivistetty #791): yhdistelmaaihesivun
   aktiiviset rajaukset. Rivi = YKSI pilli jossa on kaksi klikattavaa
   puoliskoa: nimipuolisko (.merkki.aihe) vie yhden aiheen sivulle, x-puolisko
   pudottaa aiheen joukosta. <li> kantaa yhteisen taustan, reunan ja
   pyoristyksen -> puoliskot luetaan yhdeksi palikaksi. (Teemun selaintesti
   11.8.2026: erilliset pyoristykset + rako tekivat x:sta irrallisen, jolloin
   sen liittyminen tagiin ei hahmottunut.)

   Puoliskot nollataan lapinakyviksi VAIN tassa scopessa -- .merkki.aihe
   -perussaanto on jaettu etusivun trendien, /aiheet-arkiston ja .plain-listan
   kanssa, eika sita saa muuttaa taman nakyman takia.

   Erotin puoliskojen valilla on taustan savyero (--color-surface ->
   --color-surface-low), ei 1px-borderi: tokenilohkon "layered paper"
   -periaate (ks. rivi 8). x:n hover jatkaa samaa portaikkoa
   (--color-surface-mid).

   EI overflow: hidden. Picon fokusrengas on box-shadow
   (:where(a:not([role=button])):focus-visible), jonka overflow: hidden
   leikkaisi kokonaan pois -> puoliskot saavat sen sijaan eksplisiittiset
   kulmapyoristykset :first-child/:last-child -valitsimilla. Kun x puuttuu
   (N=1, ei poisto_href) sama elementti on seka ensimmainen etta viimeinen ->
   yksinainen pilli pyoristyy kaikista kulmista eika jaa puolikkaaksi.

   Kosketuskohde: x sailyy 2.5rem x 2.5rem (=40x40px) vaikka visuaali on
   tiivis; align-items: stretch venyttaa nimipuoliskon samaan korkeuteen. */
.tag-list.active-filters {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-3);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.tag-list.active-filters li {
  display: flex;
  align-items: stretch;
  min-height: 2.5rem;
  margin: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}
/* Puoliskojen nollaus: tausta, reuna ja pyoristys tulevat <li>:lta. */
.tag-list.active-filters li > * {
  background: transparent;
  border: 0;
  border-radius: 0;
}
.tag-list.active-filters li > :first-child {
  border-top-left-radius: var(--radius-sm);
  border-bottom-left-radius: var(--radius-sm);
}
.tag-list.active-filters li > :last-child {
  border-top-right-radius: var(--radius-sm);
  border-bottom-right-radius: var(--radius-sm);
}
.tag-list.active-filters .poista-rajaus {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.5rem;
  min-height: 2.5rem;
  line-height: 1;
  color: var(--color-muted);
  text-decoration: none;
  background: var(--color-surface-low);
  transition: background var(--transition-fast), color var(--transition-fast);
}
.tag-list.active-filters .poista-rajaus:hover,
.tag-list.active-filters .poista-rajaus:focus-visible {
  background: var(--color-surface-mid);
  color: var(--color-text);
}
/* Aliakset-alarivi (#416): canonical-tagin alle "myos: a, b, c". */
.tag-list .aliakset {
  display: block;
  margin-top: var(--space-1);
  color: var(--color-muted);
  font-family: var(--font-label);
  font-size: .6875rem;
  line-height: var(--line-height-tight);
}

/* Harvinaisten tagien piilotus oletuksena (#435).
   .harvinainen-luokka annetaan <li>-elementille jos artikkelimaara < 2
   (PHP: tagit-komponentti, prop $harvinainen_kynnys). .nayta-kaikki -toggle
   listalla paljastaa kaikki. .haun-osuma -luokka voittaa piilotuksen kun
   hakuinput osuu piilotettuun riviin -- nain hauku loytaa harvinaisetkin
   ilman etta toggle pitaa kayttaa. */
.tag-list.alphabetical:not(.nayta-kaikki) .harvinainen:not(.haun-osuma) {
  display: none;
}

/* Harvinaiset-toggle (#435 nappina -> #1351 linkkina).
   Rajaus tulee nyt URLista (?kaikki=1), koska harvinaisia ei enaa renderoida
   DOMiin -- JS-toggle ei voisi paljastaa riveja joita ei ole. Sama ulkoasu
   molemmille elementeille jotta vaihto ei nay kayttajalle. */
.harvinaiset-toggle {
  margin-bottom: var(--space-3);
}
.harvinaiset-toggle button,
.harvinaiset-toggle a {
  display: inline-block;
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-1) var(--space-3);
  font: inherit;
  cursor: pointer;
  color: var(--color-text);
  text-decoration: none;
}
.harvinaiset-toggle button:hover,
.harvinaiset-toggle a:hover {
  background: var(--color-surface-mid);
}
.harvinaiset-toggle button[aria-pressed="true"] {
  background: var(--color-surface-mid);
}

.aakkos-ankkurit {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  padding: var(--space-2) 0;
  margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
.aakkos-ankkurit a {
  padding: var(--space-1) var(--space-2);
  text-decoration: none;
  font-weight: 600;
}
.aakkos-haku {
  margin-bottom: var(--space-3);
}
.aakkos-haku input {
  width: 100%;
  max-width: 480px;
}

/* Kategoriafasettipalkki (#583) -- /aiheet tag-list view=filter-bar.
   Napit tilaluokalla .aktiivinen (No-BEM). Vaakavieritys mobiililla jos
   napit eivat mahdu; desktopilla kietoutuvat. */
.kategoria-suodatin {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.kategoria-suodatin button {
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  padding: var(--space-1) var(--space-3);
  font: inherit;
  cursor: pointer;
  color: var(--color-text);
}
.kategoria-suodatin button:hover {
  background: var(--color-surface-mid);
}
.kategoria-suodatin button.aktiivinen {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-bg);
}

/* --- klusteri-hero ---
   Ison klusterin korostuskortti etusivulla. */

.klusteri-hero header {
  margin-bottom: var(--space-3);
}
.klusteri-hero h2 {
  margin-top: 0;
  margin-bottom: var(--space-1);
}
.klusteri-hero .meta {
  font-size: .875rem;
  color: var(--color-muted);
}
.klusteri-hero .meta .erotin {
  color: var(--color-border);
  margin: 0 var(--space-1);
}
.klusteri-hero .artikkelit {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: var(--space-3) 0;
}
.klusteri-hero footer {
  margin-top: var(--space-3);
}

/* --- paivan-pahimmat ---
   Etusivun nostolaatikko: 24h sisalla korkeimmat bias_score -artikkelit.
   Reusaa .kortti + .artikkelit -rakenteet artikkelikortti-listausta varten.
   body.sivu-etusivu lisaa valkoisen taustan + shadow-sm (sama kuin klusterit). */

.paivan-pahimmat header {
  margin-bottom: var(--space-3);
}
.paivan-pahimmat h2 {
  margin-top: 0;
  margin-bottom: var(--space-1);
}
.paivan-pahimmat .meta {
  font-size: .875rem;
  color: var(--color-muted);
  margin: 0;
}
.paivan-pahimmat .meta .erotin {
  color: var(--color-border);
  margin: 0 var(--space-1);
}
.paivan-pahimmat .artikkelit {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
}

/* --- esitetyt-faktat ---
   Artikkelisivun (Vinouma-tab) "Esitetyt faktat" -laatikko (task #236, 7.5.2026).
   Listaa analysis.claimedFacts-rivit + visuaalisen lähde-indikaattorin (.merkki).
   Reusaa .kortti-rakennetta. Värivalinta on informatiivinen, ei tuomitseva —
   keltainen "Ei lähdettä" ei ole varsinainen varoitus. */

.esitetyt-faktat header {
  margin-bottom: var(--space-3);
}
.esitetyt-faktat h3 {
  margin-top: 0;
  margin-bottom: var(--space-1);
}
.esitetyt-faktat .johdanto {
  font-size: .875rem;
  color: var(--color-muted);
  margin: 0;
}
.esitetyt-faktat .lista {
  list-style: none;
  padding: 0;
  margin: var(--space-3) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* Pico-luodin vaimennus <li>-tasolla (vahti: --group=frontend). */
.esitetyt-faktat .lista li { list-style: none; }
.esitetyt-faktat .rivi {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.esitetyt-faktat .vaite {
  margin: 0;
  color: var(--color-text);
  line-height: 1.5;
}
.esitetyt-faktat .lahde-tieto {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.esitetyt-faktat .lahde-attribuutio {
  font-style: italic;
  color: var(--color-muted);
  font-size: .875rem;
}
.esitetyt-faktat .cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.esitetyt-faktat .cta .muted {
  font-size: .8rem;
}

/* --- vinouma-palkki ---
   Kolmiosainen progress-bar vinouma-jakaumalle. */

.vinouma-palkki {
  display: flex;
  width: 100%;
  height: 8px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--color-surface);
  margin: var(--space-2) 0;
}
.vinouma-palkki .osa {
  height: 100%;
  transition: width 0.3s ease;
}
.vinouma-palkki .osa.matala {
  background: var(--color-bias-low);
}
.vinouma-palkki .osa.kohtalainen {
  background: var(--color-bias-medium);
}
.vinouma-palkki .osa.korkea {
  background: var(--color-bias-high);
}

/* --- laatu-jakauma ---
   Kolmiosainen progress-bar journalistic_score-jakaumalle (composite).
   Sama wrapper-rakenne kuin .vinouma-palkki, KÄÄNTEINEN väriskaala:
   hyva (≥70) = vihreä, keskitaso (40-69) = keltainen, heikko (<40) = punainen.
   D54 Vaihe 1 etusivu (#437, 23.5.2026). */

.laatu-jakauma {
  display: flex;
  width: 100%;
  height: 8px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--color-surface);
  margin: var(--space-2) 0;
}
.laatu-jakauma .osa {
  height: 100%;
  transition: width 0.3s ease;
}
.laatu-jakauma .osa.hyva {
  background: var(--color-bias-low);
}
.laatu-jakauma .osa.keskitaso {
  background: var(--color-bias-medium);
}
.laatu-jakauma .osa.heikko {
  background: var(--color-bias-high);
}

/* --- klusteriruudukko ---
   Grid sekundaarisille klusterikorteille. */

.klusteriruudukko {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 768px) {
  .klusteriruudukko {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* --- klusterikortti ---
   Pienempi klusterikortti gridissä. */

.klusterikortti header {
  margin-bottom: var(--space-2);
}
.klusterikortti h3 {
  margin-top: 0;
  margin-bottom: var(--space-1);
}
.klusterikortti .meta {
  font-size: .8125rem;
  color: var(--color-muted);
}
.klusterikortti .meta .erotin {
  color: var(--color-border);
  margin: 0 var(--space-1);
}
.klusterikortti .artikkelit {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: var(--space-2) 0;
}
.klusterikortti footer {
  margin-top: var(--space-2);
}

/* --- sivu-etusivu ---
   Tonal-ryhti: body saa surface-low -taustan (hiukan harmahtava), klusterikortit
   pysyvat valkoisina (--color-bg) -> visuaalinen erotus ilman 1px-borderia
   (no-line-periaate, spec/frontend-css.md periaate 9). Sektioiden valit
   korostuneet hengitystilaa varten. */

body.sivu-etusivu {
  background: var(--color-surface-low);
}
body.sivu-etusivu main {
  padding-top: var(--space-4);
  padding-bottom: var(--space-5);
}
body.sivu-etusivu .klusteri-hero,
body.sivu-etusivu .klusterikortti,
body.sivu-etusivu .paivan-pahimmat {
  background: var(--color-bg);
  border: none;
  box-shadow: var(--shadow-sm);
  padding: var(--space-4);
}
body.sivu-etusivu .vinoutuneet-aiheet a {
  background: var(--color-bg);
  padding: var(--space-3);
  text-decoration: none;
  color: inherit;
}
body.sivu-etusivu .vinoutuneet-aiheet a:hover {
  background: var(--color-surface);
}
body.sivu-etusivu .nain-se-toimii .kortti {
  background: var(--color-bg);
  border: none;
  box-shadow: var(--shadow-sm);
}
/* home.php: klusterikortin "Kaikki artikkelit" -footer-linkki (korvaa .link-plain .text-sm) */
body.sivu-etusivu .klusterikortti footer a,
body.sivu-etusivu .klusterikortti footer .lue-lisaa { font-size: 0.875rem; text-decoration: none; }
/* home.php: osio-intro vinoutuneet-aiheet -sectionissa (tyhja-tila: perussaanto, #790) */
body.sivu-etusivu .osio-intro { font-size: 0.875rem; color: var(--color-muted); }
/* Desktop: isommat vali-spacingit sektioiden valiin. */
@media (min-width: 768px) {
  body.sivu-etusivu .klusteri-hero,
  body.sivu-etusivu .klusterikortti,
  body.sivu-etusivu .paivan-pahimmat {
    padding: var(--space-5);
  }
}

/* --- vinoutuneet-aiheet ---
   Lista aiheista joissa korkea vinouma. */

.vinoutuneet-aiheet {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.vinoutuneet-aiheet a {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  transition: background var(--transition-fast);
}
.vinoutuneet-aiheet a:hover {
  background: var(--color-surface);
}
/* min-width + overflow-wrap: flex-itemin oletus `min-width: auto` esti kutistumisen
   min-contentin alle, joten pitka katkeamaton aiheen nimi ("Paivittaistavarakauppa")
   tyonsi nowrap-metan nakyman ulkopuolelle → sivulle vaakaylivuoto (mitattu 49 px
   360 px:n nakymassa, #1271). Vaakaylivuoto siirtaa mobiilissa kelluvan
   .analyysijonon nakyman oikean reunan yli. `anywhere` (ei `break-word`) on
   valittu koska vain se pienentaa myos min-content-leveytta. */
.vinoutuneet-aiheet .nimi {
  font-weight: 500;
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}
.vinoutuneet-aiheet .meta {
  font-size: .75rem;
  color: var(--color-muted);
  white-space: nowrap;
}
.vinoutuneet-aiheet .meta .erotin {
  color: var(--color-border);
  margin: 0 var(--space-1);
}

/* --- vinouma-aareva ---
   Kaksi saraketta: vinoutuneimmat vs neutraaleimmat. */

.vinouma-aareva {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
.vinouma-aareva .sarake {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
@media (min-width: 768px) {
  .vinouma-aareva {
    grid-template-columns: 1fr 1fr;
  }
}

/* --- viikon-listat (D54 Vaihe 1 #437, etusivu) ---
   Kaksi saraketta: viikon parhaat (composite) vs vinoutuneimmat (bias).
   Sama mobile-first 1fr → 2× 1fr -kuvio kuin .vinouma-aareva. */

.viikon-listat {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
.viikon-listat .sarake {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
@media (min-width: 768px) {
  .viikon-listat {
    grid-template-columns: 1fr 1fr;
  }
}
/* Viikon laatu-jakauma -alaosio */
.viikon-laatu-jakauma .osio-alaotsikko small {
  font-weight: 400;
  color: var(--color-muted);
  margin-left: var(--space-1);
}

/* --- sivu-analysoi: sivukohtaiset override (analyze.php) ---
   Keskittaa osion otsikon "Viimeksi analysoitu" -rivin. */
body.sivu-analysoi .osion-otsikko { text-align: center; }

/* --- sivu-kirjoittaja + sivu-media: jaetut sivukohtaiset override ---
   Author.php ja media.php jakavat saman sidebar- ja valilehti-rakenteen.
   Sidebarin sisainen text-sm/link-plain -refaktorointi. Tyhja-tilat ja
   .info-teksti tulevat perussaannosta (osio 6, #790). */
body.sivu-kirjoittaja .sidebar-item a,
body.sivu-media .sidebar-item a { text-decoration: none; color: inherit; }
body.sivu-kirjoittaja .sidebar-item span,
body.sivu-media .sidebar-item span { font-size: 0.875rem; color: var(--color-muted); }
/* Drill-downin media-/kirjoittaja-jakauma: ankkuri selitteessa ilman alleviivausta */
body.sivu-kirjoittaja .porautumis-selite a,
body.sivu-media .porautumis-selite a,
body.sivu-aihe .porautumis-selite a,
body.sivu-media .porautumis-lista .rivi a { text-decoration: none; }
body.sivu-media .porautumis-lista .rivi span { font-size: 0.875rem; color: var(--color-muted); }

/* --- sivu-aihe: taulukossa media-linkki (topic.php) ---
   AI-yhteenvedon .info-teksti tuli aiemmin taalta; nyt perussäännöstä (osio 6, #790). */
body.sivu-aihe table a { text-decoration: none; color: inherit; }

/* --- sivu-artikkeli: article.php -erityissaanti (body-luokka jo kaytossa) ---
   Yhteista: meta-rivi, perustelut, asiantuntija-kortit, propagandakuvaukset.
   (Tyhjat tilat: perussääntö osiossa 6, #790.)
   Korvaa 40+ kpl text-sm/muted-text/link-plain/text-caption -yhdistelmia. */
/* Meta-rivin linkit (media, kirjoittaja, versiot, alkuperainen) */
body.sivu-artikkeli .meta a { text-decoration: none; }
body.sivu-artikkeli .meta .alkuperainen-linkki { font-size: 0.875rem; }
body.sivu-artikkeli .versiot .lista a { text-decoration: none; }
/* Artikkeli-esikatselun p */
body.sivu-artikkeli .artikkeli-esikatselu p { font-size: 0.875rem; }
/* Nakokulma-banneri */
body.sivu-artikkeli .nakokulma-kuvaus { font-size: 0.875rem; color: var(--color-muted); }
/* Vinouma-tabin sisaiset tekstit */
body.sivu-artikkeli .pisteet-selite,
body.sivu-artikkeli .kehystys-otsikko,
body.sivu-artikkeli .kehystys-perustelu,
body.sivu-artikkeli .tunne-perustelu,
body.sivu-artikkeli .propaganda-kuvaus,
body.sivu-artikkeli .omaperaisyys-perustelu { font-size: 0.875rem; color: var(--color-muted); }
body.sivu-artikkeli .pisteet-selite,
body.sivu-artikkeli .kehystys-otsikko { font-size: 0.75rem; }
body.sivu-artikkeli .propaganda-rivi .tekniikan-nimi { font-size: 0.875rem; }
/* #878: metatietolohkon maarittelylista on samaa metatekstia kuin tyhjat tilat.
   #790: .tyhja-tila irrotettu talta rivilta — se saa saman koon ja varin
   perussaannosta (osio 6). Deklaraatio jaa dl:lle, joka ei ole .info-teksti
   eika .tyhja-tila (font-size-kynnys, test/css-velka-merkit.json). */
body.sivu-artikkeli .artikkeli-metatiedot dl { font-size: 0.875rem; color: var(--color-muted); }
/* JSN-linkki */
body.sivu-artikkeli .jsn-linkki { font-size: 0.875rem; }
/* Asiantuntijakortit */
body.sivu-artikkeli .asiantuntija-titteli,
body.sivu-artikkeli .asiantuntija-konteksti,
/* #746: tutkimushetki + lahteet ovat metatietoa — sama muted-sävy ja koko kuin
   titteli. Liitetty olemassa olevaan selektorilistaan, koska font-size-kynnys
   (test/css-velka-merkit.json) on lukossa eika uusia deklaraatioita lisata.
   #877: `.asiantuntija-lahteet` -> `.lahteet` — sama lista renderöidään nyt
   myös briiffin alle (`lahteet`-komponentti), joten luokka ei voi olla
   asiantuntijakohtainen. Sääntö pysyy sivukohtaisena overridena eikä siirry
   omaksi lohkokseen: uusi standalone-deklaraatio olisi ollut +1 font-size
   kynnyksen 232 alla (nykytaso 230). */
body.sivu-artikkeli .asiantuntija-tutkittu,
body.sivu-artikkeli .lahteet,
/* #1434: aihesivun Julkinen rooli -kortti (henkilo-tausta) saa samat saannot.
   Rajattu .henkilo-taustan sisaan, jotta aihesivun muut lahdelistat eivat
   muutu. Selektorit liitetty olemassa oleviin listoihin: ei uusia
   font-size-deklaraatioita (test/css-velka-merkit.json-kynnys).
   Kirjautumattoman titteliesikatselu kirjautumisen-takana-kehotteessa
   saa saman tittelisaannon (#1434, 24.9.). */
body.sivu-aihe .henkilo-tausta .asiantuntija-titteli,
body.sivu-aihe .kirjautumisen-takana .asiantuntija-titteli,
body.sivu-aihe .henkilo-tausta .asiantuntija-tutkittu,
body.sivu-aihe .henkilo-tausta .lahteet { font-size: 0.875rem; color: var(--color-muted); }
body.sivu-artikkeli .asiantuntija-osaaminen,
body.sivu-artikkeli .asiantuntija-sidonnaisuudet,
body.sivu-artikkeli .asiantuntija-hakulinkki,
body.sivu-aihe .henkilo-tausta .asiantuntija-osaaminen,
body.sivu-aihe .henkilo-tausta .asiantuntija-sidonnaisuudet,
body.sivu-aihe .henkilo-tausta .asiantuntija-hakulinkki { font-size: 0.875rem; }
/* Tutkimushetki ja uusintanappi samalla rivilla; nappi kietoutuu alle kapealla. */
body.sivu-artikkeli .asiantuntija-tutkittu,
body.sivu-aihe .henkilo-tausta .asiantuntija-tutkittu { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
/* Lahdelista: ei luettelomerkkeja eika sisennysta — lyhyt metalista, ei sisaltoa. */
body.sivu-artikkeli .lahteet ul,
body.sivu-aihe .henkilo-tausta .lahteet ul { list-style: none; padding-left: 0; margin: var(--space-1) 0 0; }
body.sivu-artikkeli .lahteet li,
body.sivu-aihe .henkilo-tausta .lahteet li { list-style: none; margin-bottom: var(--space-1); }
/* Henkilokortin navigointilinkit (#1217, palautteet #263/#266/#289).
   EI OMAA FONT-SIZEA EIKA VARIA: linkin typografia tulee jaetusta
   `.kompakti-linkki`-perusluokasta (r. 938, #1129) — sama UI-palikka = sama
   toteutus. Tama saanto antaa VAIN listan asettelun, eli sen mika on aidosti
   taman lohkon omaa (spec/frontend-css.md § Jaa myos ASETTELU).
   Sivutuotteena font-size-kynnys (test/css-velka-merkit.json, 230/232) ei
   liiku lainkaan — perusluokan uusiokaytto oli halvempi kuin uusi deklaraatio.

   Pystysuunta eika rivi: kolme linkkia vierekkain kietoutuisi 360 px:lla
   epamaaraisesti kahdelle riville, ja palaute #289 on nimenomaan mobiilista.
   Ylamarginaali tulee `mt-sm`-utilityluokalta komponentissa.
   Ei <ul>: naiden `list-style`-vahti (frontend-vahti [6/6]) koskee listoja,
   ja `.kontekstilinkit` on jo ennakkotapaus ankkurisisaruksista ilman listaa.

   ⚠️ EI `body.sivu-artikkeli`-ETULIITETTA, ja se on mittaustulos eika tyyli-
   valinta. Mitattu tuotannosta 4.9.2026 (deploy 70f74dad): artikkelisivun
   `<body class="">` on TYHJA — `src/Views/article.php:794` kutsuu
   `component('layout', ...)` ilman `bodyClass`-propsia, ja `layout.php:59`
   lukee propsia eika `$meta->bodyClass`ia, joten `PageMetaFactory::article`in
   asettama 'sivu-artikkeli' (r. 86) ei paady koskaan perille. Sivukohtainen
   etuliite olisi siis RAKENTEELLISESTI KUOLLUT saanto: linkit renderoityivat
   `display: inline` ja kolmas kietoutui kesken tekstin 322 px:n kortissa.
   Sama juurisyy selittaa miksi `.jsn-linkki` on 16 px eika 0.875rem.

   ⚠️ ALA "SIISTI" TATA TAKAISIN SIVUKOHTAISEKSI edes sen jalkeen kun
   juurisyy on korjattu: `henkilo-linkit` on domain-komponentti jota voi
   kutsua muualtakin kuin artikkelisivulta, joten sivusidonnaisuus olisi
   vaara rajaus muutenkin. Juurisyy (37 kuollutta `body.sivu-artikkeli`
   -saantoa, joiden aktivointi kerralla on katselmoimaton visuaalinen muutos
   tuoteytimessa) on nostettu omaksi taskikseen — sita EI korjata tassa. */
.henkilo-linkit { display: flex; flex-direction: column; gap: var(--space-1); }
/* Aiheesta muualla -haku */
body.sivu-artikkeli .haku-ohje { font-size: 0.875rem; color: var(--color-muted); }
body.sivu-artikkeli .kentta-otsikko { font-size: 0.875rem; font-weight: 600; }
body.sivu-artikkeli .haku-tulos-meta { font-size: 0.875rem; color: var(--color-muted); }
body.sivu-artikkeli .hakutunnisteet .lukumaara { font-size: 0.75rem; color: var(--color-muted); }
/* Pimento-ilmoitus */
body.sivu-artikkeli .pimento-vinkki { font-size: 0.875rem; color: var(--color-muted); }
body.sivu-artikkeli .pimento-kiitos { font-size: 0.875rem; color: var(--color-success); }

/* --- sivu-pimento-tulos: blackout-result.php -erityissaanti ---
   Detaljiteks­tit, tekniset tiedot ja suodatetut hakutulokset (muted + pieni teksti).
   Korvaa 37+ kpl .text-sm.muted-text -yhdistelmia alkuperaisessa naytossa. */
body.sivu-pimento-tulos .alaotsikko,
body.sivu-pimento-tulos .edistyma,
body.sivu-pimento-tulos .virheviesti,
body.sivu-pimento-tulos .lisaselitys,
body.sivu-pimento-tulos .api-haku-rivi,
body.sivu-pimento-tulos .pimento-yhteenveto,
body.sivu-pimento-tulos .haitari > summary .haitari-otsikko,
body.sivu-pimento-tulos .tyokalu-sarake,
body.sivu-pimento-tulos .lahde,
body.sivu-pimento-tulos .haku-arvo { font-size: 0.875rem; color: var(--color-muted); }
body.sivu-pimento-tulos .perustelu { font-size: 0.875rem; }
body.sivu-pimento-tulos .tulos-rivi,
body.sivu-pimento-tulos .haku-otsikko { font-size: 0.875rem; }
body.sivu-pimento-tulos .kattavuusvaje { font-size: 0.875rem; }
body.sivu-pimento-tulos details .kortti .kentan-nimi { color: var(--color-muted); }
/* Edistyma-paketti on block-level, joten sailyy blokkina mutta pienena */
body.sivu-pimento-tulos .edistyma { display: block; }
/* Tehdyt haut -taulukko: pitkat hakutermit rivittyvat, ettei taulukko pakota
   sivua vaakascrolliin kapealla naytolla (#535). overflow-wrap: anywhere
   vaikuttaa myos solun min-content-leveyteen — break-word ei riittaisi
   table-layoutissa. */
body.sivu-pimento-tulos .tehdyt-haut td { overflow-wrap: anywhere; }

/* --- sivu-palvelu: palvelu.php markkinointisivun paketoitu override ---
   5 x .palvelu-osio, kaikki symmetrisia (keskitetty h2, palvelu-lead, kortit).
   Korvaa 14 text-sm + 17 text-center yhdella pakettimaisella override-lohkolla. */
body.sivu-palvelu .palvelu-osio { text-align: center; }
body.sivu-palvelu .palvelu-osio .palvelu-lead { color: var(--color-muted); }
body.sivu-palvelu .palvelu-osio .kortti.compact p { font-size: 0.875rem; color: var(--color-muted); }
body.sivu-palvelu .palvelu-osio .jalkikirjoitus {
  font-size: 0.875rem; color: var(--color-muted); margin-top: var(--space-4);
}

/* --- sivu-tilannekatsaus: palvelu-osioiden keskityksen "paketoitu" override ---
   Sivulla 7 sectionia, kaikki samannakoisia (keskitetty h2, palvelu-lead,
   compact-korttien meta-p). Yksi override paketoi koko sivun esityksen. */
body.sivu-tilannekatsaus .palvelu-osio { text-align: center; }
body.sivu-tilannekatsaus .palvelu-osio .palvelu-lead { color: var(--color-muted); }
body.sivu-tilannekatsaus .palvelu-osio .kortti .palvelu-lead,
body.sivu-tilannekatsaus .palvelu-osio .kortti > p { font-size: 0.875rem; color: var(--color-muted); }
body.sivu-tilannekatsaus .palvelu-osio .kortti .muted-poikkeus { color: var(--color-muted); }

/* --- sivu-yrityksille: vasemmalle tasattu keissiländäri (#1156) ---
   `.palvelu-lead` on `margin: … auto …` eli keskitetty palsta. Se on oikein
   `sivu-palvelu`lla ja `sivu-tilannekatsaus`illa, joissa KOKO osio on
   keskitetty. Tällä sivulla osiot ovat vasemmalle tasattuja (h2 + taulukot +
   palkkilistat lukevat riveinä), jolloin keskitetty selite jää kellumaan
   otsikkonsa alle eri linjaan. Rivin mitta (640 px) säilyy — vain vasen
   marginaali nollataan, ei `max-width`ia. */
body.sivu-yrityksille .palvelu-osio .palvelu-lead { margin-left: 0; }

/* author.php + media.php + topic.php analytiikka-teaseri (kirjautumaton tila) */
.analytiikka-teaseri { text-align: center; padding: var(--space-5); }
.analytiikka-teaseri p { color: var(--color-muted); }

/* --- nain-se-toimii ---
   Accordion + step-grid etusivun alaosassa (home.php, analyze.php). */

.nain-se-toimii .ruudukko {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
.nain-se-toimii .numero {
  font-size: 2rem;
  font-weight: 700;
  color: var(--color-primary);
  font-family: var(--font-editorial);
  margin-bottom: var(--space-2);
}
/* Kortti keskitetty + kortin p.muted (korvaa text-center + text-sm muted-text) */
.nain-se-toimii .kortti { text-align: center; }
.nain-se-toimii .kortti p { font-size: 0.875rem; color: var(--color-muted); }
@media (min-width: 768px) {
  .nain-se-toimii .ruudukko {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* --- pimento-kortti ---
   Blackout/pimento-kortin rakenne. */

.pimento-lista {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.pimento-kortti .otsikkorivi {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.pimento-kortti h3 {
  margin: 0;
  font-size: 1.1rem;
}
.pimento-kortti .laskuri {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--color-muted);
  line-height: 1;
  flex-shrink: 0;
}
.pimento-kortti .laskuri.varoitus {
  color: var(--color-danger);
}
.pimento-kortti .aiheet {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-bottom: var(--space-2);
}
.pimento-kortti .meta {
  margin-bottom: var(--space-2);
}
/* blackouts.php meta/footer/muistiinpanot: korvaa .text-sm.muted-text -utilityt */
.pimento-kortti .meta span,
.pimento-kortti footer span { font-size: 0.875rem; color: var(--color-muted); }
.pimento-kortti footer {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}
.pimento-kortti .muistiinpanot {
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
}
.pimento-kortti .muistiinpanot small { color: var(--color-muted); }

/* blackouts.php tyhja-tila (sivu-pimennot body-luokalla) */
body.sivu-pimennot .tyhja-tila { text-align: center; }

/* blackouts.php .page-header .alaotsikko: korvaa .muted-text */
.page-header .alaotsikko { color: var(--color-muted); }

/* --- ilmiolista (#864 osa B) ---
   Ilmiorivit (/tilannekatsaus, etusivun tutki-ilmioita, artikkelisivu,
   /aihe/). `.ilmio-nosto` on <article>, jolle Pico antaa KORTIN (padding,
   varjo, radius) — aiempi kommentti vaitti "ei korttikehysta", mutta kehys
   on ollut nakyvissa koko ajan (mitattu selaimessa 25.9.2026, #1442).
   Asettelu on olemassa oleva `.korttiruudukko.lohkot` (#1440, luokka tulee
   komponentin markupista): leveassa nakymassa kaksi saraketta, kapeassa yksi,
   ilman breakpointia. Ruudukon gap hoitaa valin, joten globaali
   `article { margin-bottom }` nollataan — muuten vali oli gap + margin = 48px
   (palaute #274). Ei omaa font-sizea: globaalit h3/p riittavat, ja
   css-velka-merkit.json font_size_max on lahella kattoa.
   Variantti tilaluokkana (.ilmiolista.otos), ei BEM. */

.ilmio-nosto {
  margin: 0;
}
.ilmio-nosto h3 {
  margin: 0 0 var(--space-1);
}
.ilmio-nosto p {
  margin: 0 0 var(--space-1);
}
.ilmio-nosto .kuvaus {
  color: var(--color-muted);
}
/* Huomiolause on rivin ydin (§ 12.1): se kertoo MITEN aiheesta uutisoitiin.
   Korostus tehdaan painolla eika koolla — kokoero vaatisi uuden font-sizen. */
.ilmio-nosto .huomiolause {
  font-weight: 600;
}
.ilmio-nosto .maara {
  margin-bottom: 0;
}

/* --- ilmio-kaaret (#1284, /politiikka lohko 4) ---
   Politiikan ilmiot rajattavalla sivulla. RAKENTEELLINEN SISARUS
   `.ilmiolista`lle mutta ERI LOHKO, ja ero on tarkoituksellinen: `.ilmiolista`
   nayttaa YHDEN luvun rivilla (Teemun paatos 14.8.2026, palautteet #208/#209)
   ja on jarjestetty sen luvun mukaan (portti `IlmiolistaRenderTest`); tama
   nayttaa kaaren + juttumaaran + puolueet ja on jarjestetty TUOREUDELLA.
   Siksi oma luokka eika `.ilmiolista.politiikka` -variantti: variantti
   perisi tyylit palikalta jonka invariantteja tama rikkoo, ja seuraava lukija
   olettaisi niiden patevan. Perustelu kokonaisuudessaan `spec/komponentit.md`
   § Tietoiset inline-toteutukset.
   Ei omaa font-sizea (globaalit h3/p riittavat), ei BEMia. */

.ilmio-kaaret {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.ilmio-kaaret h3 {
  margin: 0 0 var(--space-1);
}
.ilmio-kaaret p {
  margin: 0 0 var(--space-1);
}
.ilmio-kaaret .kuvaus {
  color: var(--color-muted);
}
.ilmio-kaaret .puolueet {
  margin-bottom: 0;
}

/* --- paiva-navigointi (#978 osa C) ---
   Eteen/taakse-siirtyma paivakohtaisten koostesivujen valilla
   (/tilannekatsaus). Ulkoasultaan sukua `.sivutus`elle mutta eri asia: siella
   liikutaan saman tulosjoukon sivujen valilla, tassa vaihdetaan paivaa eli
   koko sivun sisaltoa. Ei omaa font-sizea — peritaan ympariston koko
   (css-velka-merkit.json font_size_max on 5 yksikon paassa katosta). */

.paiva-navigointi {
  display: flex;
  align-items: center;
  /* Nuolet rivin laitoihin: reunapaivalla disabloitu alkio pitaa paikkansa,
     joten jaljella oleva nuoli ei hyppaa paivaa vaihdettaessa. Pico-CSS:n
     `nav { justify-content: space-between }` antaisi saman, mutta se
     kirjoitetaan tahan nakyviin — ilman rivia sailion tyyli olisi Picon
     varassa (vrt. .valintalinkit joka joutuu kumoamaan saman saannon). */
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin: var(--space-4) 0;
}
.paiva-navigointi .paiva {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-muted);
  text-decoration: none;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}
.paiva-navigointi a.paiva:hover {
  background: var(--color-surface);
  color: var(--color-text);
}
/* Sama tilaluokka kuin `.sivutus`illa: reunapaiva ei ole klikattava eika
   nayta silta. `pointer-events: none` ei ole tarpeen — alkio on <span>. */
.paiva-navigointi .paiva.disabloitu {
  opacity: 0.4;
}

/* Paivavalitsin (#1128) — nuolten valissa, `space-between` asettaa keskelle.

   MOLEMMAT SAANNOT KUMOAVAT PICON OLETUKSEN eivatka ole koristetta: Pico antaa
   `input { width: 100% }` ja `label { display: block }`, joten ilman naita
   kentta venyisi koko riville ja tyontaisi nuolet omille riveilleen.

   SPESIFISYYS NAKYVIIN: Picon `input,select,textarea` on (0,0,1) ja
   `.paiva-navigointi .paiva-valitsin` on (0,2,0) -> voittaa ilman
   `!important`ia. Selektori on LUOKKAPOHJAINEN, EI `input[type="date"]`:
   tyyppiin sidottu saanto lakkaa osumasta hiljaa jos tyyppi joskus vaihtuu
   (#656/#674 `.hero-haku`, sama ansa `.hakukentta`ssa).

   Ei omaa font-sizea — peritaan ympariston koko, kuten `.paiva`-napeissa
   (css-velka-merkit.json font_size_max on 5 yksikon paassa katosta). */
.paiva-navigointi .paiva-valitsin-label {
  display: inline-flex;
  align-items: center;
  margin: 0;
  cursor: pointer;
}
.paiva-navigointi .paiva-valitsin {
  width: auto;
  margin: 0;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-muted);
  font-family: var(--font-body);
}
.paiva-navigointi .paiva-valitsin:hover {
  color: var(--color-text);
}

/* --- vihje ---
   Info-ikoni popoverilla. Kehys-komponentti (domain-riippumaton).
   Kaytto: pieni i-nappi otsikon vieressa, @click avaa selitteen. */

.vihje {
  position: relative;
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  margin-left: var(--space-1);
}
.vihje-trigger {
  display: inline-flex;
  align-items: center;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  opacity: 0.4;
  transition: opacity var(--transition-base);
  color: inherit;
  margin: 0;
}
.vihje-trigger:hover { opacity: 1; }
.vihje-sisalto {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 10;
  background: var(--color-surface-low);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  font-size: .75rem;
  font-weight: 400;
  line-height: 1.5;
  min-width: 200px;
  /* Viewport-clamp (UX-bugifix 11.6.2026): takaa etta popover mahtuu
     nayttoon kapeillakin ruuduilla (<336px). vihje.php:n asemoi() siirtaa
     popoverin translateX:lla viewportin sisaan kun ankkuri (left:0 /
     right:0) tyontaisi sen reunan yli — clamp varmistaa etta siirron
     jalkeen koko sisalto mahtuu 8px marginaalein. Sama clamp koskee kaikkia
     vihje-popovereita, myos raati-varianttia (#647). */
  max-width: min(320px, calc(100vw - 16px));
  box-shadow: var(--shadow-float);
  white-space: normal;
}

/* Kortti/mittarikortti/porautumis-kortti suoran lapsena -> oikea ylakulma
   (ei inline-flow). .kortti saa position: relative alempana kortti-lohkossa,
   .mittarikortti on jo position: relative, .porautumis-kortti saa position
   alempana porautumis-kortti-lohkossa. Inline-kaytto (esim.
   <h3>teksti <vihje/></h3>) ei osu tahan selektoriin, joten virtaus otsikon
   vieressa sailyy. */
.kortti > .vihje,
.mittarikortti > .vihje,
.porautumis-kortti > .vihje {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  margin-left: 0;
  z-index: 2;
}

/* Tooltip asemoituu oikean reunan mukaan kun trigger on oikeassa ylakulmassa,
   muuten popover leikkaantuisi kortin oikeasta reunasta. */
.kortti > .vihje .vihje-sisalto,
.mittarikortti > .vihje .vihje-sisalto,
.porautumis-kortti > .vihje .vihje-sisalto {
  right: 0;
  left: auto;
}

/* Auki-tila (#664, palaute #92): yo. wrapper on position:absolute + z-index:2 =
   OMA PINOUTUMISKONTEKSTI, joten popoverin z-index:10 vaikuttaa vain sen sisalla
   — ulospain koko wrapper on tasolla 2. Kortit itse ovat position:relative ilman
   z-indexia (eivat luo kontekstia), joten kaikkien korttien wrapperit kilpailevat
   samalla tasolla ja DOM-jarjestys ratkaisee: mobiilissa .mittarit on
   2-sarakkeinen ruudukko, jolloin seuraavan kortin i-nappi piirtyi avatun
   popoverin paalle. Nosto tehdaan VAIN auki-tilassa (Alpine sitoo .auki:n samaan
   open-muuttujaan), jolloin suljettuna pinoutuminen sailyy ennallaan.
   Arvo 30: voittaa naapuriwrapperit (2) ja kortin sisaiset kerrokset (.versiot,
   .premium-ilmoitus = 10), mutta jaa selvasti alle kelluvien (analyysijono 50,
   .kontekstikaista 99, .ylapalkki 100, dropdown/turnstile 110, modaali 200).
   Sama nosto .raati-vihje-variantille alempana — molemmat on paivitettava. */
.kortti > .vihje.auki,
.mittarikortti > .vihje.auki,
.porautumis-kortti > .vihje.auki {
  z-index: 30;
}

/* --- raati-vihje ---
   Tuomariraadin pisteet i-ikonista (task #508). Uudelleenkayttaa .vihje-popoverin
   mekaniikkaa ja tyylia: trigger = .vihje-trigger, popover-runko = .vihje-sisalto
   (tausta, border, padding, font-size peritaan). EI rinnakkainen mekaniikka.
   Tama lohko lisaa vain raati-spesifin: wrapper-positiointi + listan rakenne +
   popoverin leveysrajaus (sisalto on rikkaampi kuin pelkka string). */
.raati-vihje {
  position: relative;
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  margin-left: var(--space-1);
}
/* Leveysrajaus: .vihje-sisalto on min-width 200 / max-width 320. Raati-popover
   kasvaa sisallon mukaan (lista + selitysteksti), siksi width: max-content.
   Leveysrajaus peritaan .vihje-sisalto:sta — EI omaa clampia (#647, 31.7.2026):
   aiempi calc(100vw - 56px) oletti ankkurin olevan 40px viewportin oikeasta
   reunasta (taysleveä kortti). Mobiilissa .mittarit on 2-sarakkeinen ruudukko,
   jolloin ankkuri on ~186px reunasta eika mikaan clamp-arvo riita. Reunan yli
   valuminen hoidetaan JS-asemoinnilla (vihje_popover_xdata() → asemoi()), joka
   mittaa todellisen sijainnin ja siirtaa popoverin translateX:lla nayttoon. */
.raati-vihje-sisalto {
  width: max-content;
}
.raati-vihje-otsikko {
  display: block;
  margin-bottom: var(--space-1);
}
.raati-aanet {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-2);
}
.raati-aanet li {
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  line-height: 1.6;
}
.raati-malli { color: var(--color-muted); }
.raati-piste { font-weight: 600; }
.raati-vihje-teksti {
  font-weight: 400;
  line-height: 1.5;
  color: var(--color-muted);
  margin: 0;
  white-space: normal;
}

/* Mittarikortin/kortin suorana lapsena → oikea ylakulma (sama kuin .vihje), popover
   asemoituu oikean reunan mukaan ettei leikkaannu kortista. */
.mittarikortti > .raati-vihje,
.kortti > .raati-vihje {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  margin-left: 0;
  z-index: 2;
}
.mittarikortti > .raati-vihje .vihje-sisalto,
.kortti > .raati-vihje .vihje-sisalto {
  right: 0;
  left: auto;
}

/* Auki-tila (#664) — sama pinoutumiskorjaus kuin .vihje:lla, perustelut siella.
   Tama variantti on mobiilissa se joka rikkoi: .mittarit-ruudukon vierekkaiset
   raati-kortit peittivat toistensa popoverit. */
.mittarikortti > .raati-vihje.auki,
.kortti > .raati-vihje.auki {
  z-index: 30;
}

/* Erimielisyysmerkinta (#1094): nakyva rivi kun raadin hajonta talla
   ulottuvuudella >= PanelReader::DISAGREEMENT_RANGE. Renderoityy .raati-vihje:n
   SISARENA (wrapper on kortin lapsena absolute, sisalla teksti kelluisi).
   EI omaa font-sizea: koko + varit tulevat .info-teksti-perussaannosta (osio 6,
   #790 — small.info-teksti .75rem + --color-muted). Tama on tarkoituksellista,
   ei laiskuutta: css-velka-merkit.json font_size_max on 232/227 ja sen _doc
   kieltaa nostot. Tama saanto lisaa siis vain asemoinnin.
   Neutraali harmaa EI varoitusvari: merkinta kertoo etta tulkinta on
   kiistanalainen, ei etta analyysi on virheellinen (spec/personality.md § Savy).
   display:block on se mika tekee tasta ennustettavan kaikissa kolmessa
   kutsukontekstissa: .otsikkoanalyysi-rivissa se estaa merkintaa jatkamasta
   badge-rivia (mobiilin ahtain kohta) ja nostaa sen perustelutekstin ylle;
   korttikonteksteissa se on jo omalla rivillaan. */
.raati-erimielisyys {
  display: block;
  margin-top: var(--space-1);
  line-height: 1.4;
  text-wrap: pretty;
}

/* --- artikkeli-tyokalupalkki ---
   Toolbar artikkelisivun ylareunassa, normaalissa flowssa. Domain-komponentti.
   EI sticky (task #549): sticky top:0 tormasi .ylapalkki:n (sticky, z-index 100)
   kanssa -> ylapalkki peitti tyokalupalkin skrollatessa (mobiilipalaute #9).
   Sticky-konteksti hoidetaan .kontekstikaista-nimikaistalla (kontekstikaista.php,
   sivuotsikon stickyPrefix/stickyTitle-propsit) — tyokalupalkin toiminnot (kopioi/ilmoita/
   jaa) eivat tarvitse jatkuvaa saatavuutta skrollin aikana.
   Napit ikoneina mobiilissa (.ikoni-mobiilissa piilottaa .painike-tekstin),
   desktopilla ikoni + teksti vierekkain.
   #878 (palaute #189): `.keski`-alue ja sen saannot poistettu AI-merkinnan
   mukana — se oli olemassa vain merkintaa varten. Palkissa on nyt yksi lapsi
   (.oikea), joka pysyy oikeassa reunassa `margin-left: auto`lla molemmissa
   koossa (nav on flex + space-between, joka yhdella lapsella tasaisi vasemmalle). */

.artikkeli-tyokalupalkki {
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-border);
  padding: var(--space-2) 0;
  /* box-shadow poistettu stickyn mukana (#549): varjo viestii kelluvaa
     kerrosta — normaalissa flowssa se olisi harhaanjohtava. */
}
.artikkeli-tyokalupalkki nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.artikkeli-tyokalupalkki .oikea {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-wrap: nowrap;
  margin-left: auto; /* tyontaa oikealle — ainoa lapsi #878:n jalkeen */
}
.artikkeli-tyokalupalkki .oikea .painike {
  padding: var(--space-1) var(--space-2);
  font-size: 0.75rem;
  white-space: nowrap;
  margin-bottom: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
/* Mobiilissa nayta vain ikoni (piilota teksti).
   Desktopilla teksti palautuu @media-queryssa. */
.artikkeli-tyokalupalkki .painike.ikoni-mobiilissa .painike-teksti {
  /* sr-only-tyylinen piilotus saavutettavuus sailyttaen — aria-label
     riittaa screen readereille, mutta pidetaan teksti DOM:ssa
     desktop-lavennusta varten */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
/* Dialog-sisallon leipateksti (kopioi prompti, ilmoita virhe) — korvaa .text-sm */
.artikkeli-tyokalupalkki dialog p { font-size: 0.875rem; }
/* Media-merkki linkkina — korvaa .link-plain */
.artikkeli-tyokalupalkki .oikea a.merkki { text-decoration: none; }

/* Desktop: napit teksteilla, isommat gapit ja nappipadding.
   `margin-left: auto` EI nollata (#878): palkissa on vain `.oikea`, joten
   nollaus tasaisi napit vasempaan reunaan. */
@media (min-width: 769px) {
  .artikkeli-tyokalupalkki nav { flex-wrap: nowrap; gap: var(--space-3); }
  .artikkeli-tyokalupalkki .oikea {
    gap: var(--space-2);
  }
  .artikkeli-tyokalupalkki .oikea .painike {
    padding: var(--space-2) var(--space-4);
    font-size: .9375rem;
  }
  /* Desktopilla teksti palautuu nakyviin */
  .artikkeli-tyokalupalkki .painike.ikoni-mobiilissa .painike-teksti {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    clip: auto;
  }
}

/* --- artikkeli-asettelu ---
   Kaksipalstainen layout: alkuperainen (vasen) + analyysi (oikea).
   Mobiilissa padding = 0, koska kutsuva sivu on `.container`-kaareessa
   joka hoitaa horisontaalisen marginaalin. Tuplapadding aiheutti
   x-overflowin mediasivulla.
   Suorat lapset saavat `min-width: 0` jotta grid-itemit eivat venyta
   asettelua kun sisalto (taulukot, narratiivit) on leveampaa. */

.artikkeli-asettelu {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  align-items: start;
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 0;
}
.artikkeli-asettelu > * { min-width: 0; }
.artikkeli-asettelu .alkuperainen {
  padding: var(--space-3);
  margin-bottom: var(--space-4);
  background: var(--color-surface-low);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.artikkeli-asettelu .analyysi {
  min-width: 0;
}

/* Desktop: kaksipalstainen layout */
@media (min-width: 1025px) {
  .artikkeli-asettelu { grid-template-columns: 1fr 1fr; }
  .artikkeli-asettelu .alkuperainen {
    padding: var(--space-4);
    margin-bottom: 0;
  }
}

/* --- syvaanalyysi ---
   Vinouma-tabin Taso 2 -haitarit full-width-laatikkona .artikkeli-asettelu-gridin
   alapuolella. Sama max-width kuin gridilla (centered .container:in sisalla).
   Nakyvissa vain kun activeTab === 'bias' (Alpine x-show, scope nostettu
   .artikkeli-asettelun ylapuolelle article.php:ssa). Mobiilissa block-elementtina
   pötköön luonnostaan, ei erillista mobiilisaatoa. Task #441. */
.syvaanalyysi {
  max-width: var(--content-max);
  margin: var(--space-5) auto 0;
}

/* --- todellisuustarkistus ---
   Todellisuustarkistus-haitari full-width-laatikkona .artikkeli-asettelu-gridin
   alapuolella, syvaanalyysin ylapuolella. Sama layout-perhe kuin .syvaanalyysi
   (sama max-width, sama ylamarginaali), siksi tassa vieressa.

   MIKSI EI VALILEHTI (#1117, palaute #260): valilehtena sen avaaminen piilotti
   koko vinouma-analyysin, ja koska jaetut linkit osoittavat #briefing-hashilla
   suoraan siihen, moni lukija ei nahnyt palvelun varsinaista sisaltoa lainkaan.
   Teemu: "vinouma-asiat pitaa nakya jatkuvasti ja todellisuustarkistus lisaa
   valiin oman haitarinsa".

   KOROSTUS lainaa ilmeen `.painike.ensisij`:lta (navy gradient + inverse-
   teksti) — se on pari jolla sivusto merkitsee ensisijaisen toiminnon kaikkialla
   muualla. Teemu 11.8.2026: "ei nayta samalta kun muut napit sivustolla, kayta
   samaa tyylia kun joku muu nappi." Tama korvaa poistetun
   `.valikkopainike.korostettu`-variantin, jonka ainoa kayttaja oli
   briefing-valilehti.

   KOLME HAARAA, KAIKKI PAKOLLISIA: `.haitari > summary:hover` (haitari-osio)
   vaihtaisi taustan harmaaksi hoverissa, ja `.haitari-otsikko` (color: text) +
   `.haitari-nuoli` (color: muted) maarittavat oman varinsa eli olisivat
   lukukelvottomia navy-taustalla. Lapsivalitsin `>` rajaa saannot taman lohkon
   OMAAN haitariin — briiffisisalto voi joskus sisaltaa omansa.

   HINTA EI KUULU SUMMARY-RIVILLE: haitarin avaaminen ei veloita mitaan (natiivi
   <details>, ei latausta). Hinta lukee sisalla krediittipainikkeessa (#747).

   .aanet (#1472, palaute #237) = "Ketka saavat aanen" -haitari heti taman alla,
   syvaanalyysin ylapuolella. Jakaa VAIN layoutin (sama max-width + ylamarginaali),
   ei korostusta: navy-summary on varattu ensisijaiselle toiminnolle. */
.aanet,
.todellisuustarkistus {
  max-width: var(--content-max);
  margin: var(--space-5) auto 0;
}
.todellisuustarkistus > .haitari > summary {
  background: var(--gradient-primary);
  color: var(--color-text-inverse);
  font-weight: 600;
}
.todellisuustarkistus > .haitari > summary:hover {
  background: var(--gradient-primary);
  box-shadow: var(--shadow-sm);
}
.todellisuustarkistus > .haitari > summary .haitari-otsikko,
.todellisuustarkistus > .haitari > summary .haitari-nuoli {
  color: var(--color-text-inverse);
}

/* --- artikkeli-nosto ---
   Above-the-fold -osio: metarivi, otsikko, mittarit, oivallus. */

.artikkeli-nosto {
  margin-bottom: var(--space-5);
}

/* Metarivi: "Yle · Uutinen · 12.4.2026" */
.artikkeli-meta {
  font-family: var(--font-label);
  font-size: .75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--color-muted);
  margin-bottom: var(--space-2);
}

/* Otsikko — artikkelisivukohtainen */
body.sivu-artikkeli h1 {
  font-family: var(--font-editorial);
  font-size: 2rem;
  font-weight: 700;
  line-height: var(--line-height-tight);
  color: var(--color-primary);
  letter-spacing: -0.01em;
  margin-bottom: var(--space-4);
}
@media (min-width: 768px) {
  body.sivu-artikkeli h1 {
    font-size: 2.5rem;
  }
}

/* Osio-otsikot (h3) tarvitsevat position:relative tooltip-laukaisimien
   absoluuttista positiointia varten. Korvaa 10 inline-tyyliä partial-tiedostoissa
   (task #457, H3-css-1 23.5.). */
body.sivu-artikkeli h3 { position: relative; }

/* --- mittarit ---
   2x2 grid, desktopissa 4x1. */

.mittarit {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
  margin-bottom: var(--space-4);
}
@media (min-width: 768px) {
  .mittarit {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* --- mittarikortti ---
   Yksittainen mittarikortti (vinouma, JSN, tunnelma, kehystys). */

.mittarikortti {
  position: relative;
  background: var(--color-bg);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  box-shadow: var(--shadow-sm);
}
.mittarikortti .otsikko {
  display: block;
  font-family: var(--font-label);
  font-size: .625rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-muted);
  margin-bottom: var(--space-1);
}
.mittarikortti .arvo {
  font-family: var(--font-editorial);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1;
  color: var(--color-primary);
}
/* Mittarikortin pisteet-arvo kayttaa merkki-varivarianttia vain varin saamiseksi —
   resetoi basen padding/radius jotta SVG-rengas nayttaa samalta kuin ennen. */
.mittarikortti .arvo.merkki {
  display: block;
  padding: 0;
  border-radius: 0;
  background: transparent;
  white-space: normal;
}
.mittarikortti .arvo.merkki.punainen  { color: var(--color-bias-high); }
.mittarikortti .arvo.merkki.keltainen { color: var(--color-bias-medium); }
.mittarikortti .arvo.merkki.vihrea    { color: var(--color-bias-low); }
.mittarikortti .arvo .pisteet-rengas { color: inherit; }
.mittarikortti.teksti {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.mittarikortti .teksti-arvo {
  font-family: var(--font-body);
  font-size: .8125rem;
  font-weight: 700;
  color: var(--color-primary);
  line-height: 1.3;
}
.mittarikortti.tyhja {
  opacity: 0.4;
}
.mittarikortti.tyhja .teksti-arvo {
  font-style: italic;
}
/* Kahden nimetyn arvon pari saman kortin sisalla (task #878, palaute #182a:
   Tunnelma + Kehystys yhdistettiin, jotta .mittarit tayttaa gridin tasan).
   Selite ENNEN arvoa: ilman sita lukija ei tieda kumpi luku on kumpi. */
.mittarikortti-pari {
  display: block;
  margin-top: var(--space-1);
}
.mittarikortti-pari .info-teksti {
  display: block;
  line-height: 1.2;
}

/* --- oivallus-kortti ---
   Ydinhavainto-kortti (tumma tausta, accent-raita). */

.oivallus-kortti {
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  border-left: var(--accent-stripe) solid var(--color-accent);
}
.oivallus-kortti p {
  margin: 0;
  font-family: var(--font-editorial);
  font-style: italic;
  font-size: 1.0625rem;
  line-height: 1.5;
  color: inherit;
}

/* Kaksi kappaletta (#1041): vaikutus lukijaan + journalistinen arvio.
   Perustila margin: 0 pitaa yksikappaleisen kortin ennallaan; vali tulee
   vain kun toinen kappale oikeasti renderoityy. */
.oivallus-kortti p + p {
  margin-top: var(--space-3);
}

/* --- artikkeli-konteksti ---
   Metatiedot + versiot + body preview + disclaimer. */

.artikkeli-konteksti {
  margin-bottom: var(--space-5);
  padding: var(--space-4);
  background: var(--color-bg);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.artikkeli-konteksti .meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin: 0 0 var(--space-3);
  font-size: .875rem;
}
.artikkeli-konteksti .meta a {
  font-size: .875rem;
}

/* --- versiot ---
   Artikkeliversioiden dropdown. */

.versiot {
  position: relative;
}
.versiot .versiot-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 0.3em 0.7em;
  font-size: .82rem;
  color: var(--color-muted);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
  line-height: 1.4;
}
.versiot .versiot-toggle:hover {
  background: var(--color-surface-low);
  border-color: var(--color-muted);
}
.versiot .versiot-ikoni {
  font-size: 1.05em;
}
.versiot .nuoli {
  font-size: .75em;
  transition: transform var(--transition-fast);
}
.versiot .nuoli.open {
  transform: rotate(180deg);
}
.versiot .lista {
  list-style: none;
  margin: 2px 0 0;
  padding: 0.3em 0;
  background: var(--color-surface-low);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  min-width: 100%;
  position: absolute;
  z-index: 10;
  box-shadow: var(--shadow-md);
}
.versiot li {
  list-style: none;
  padding: 0.35em 0.75em;
  font-size: .82rem;
  color: var(--color-muted);
}
.versiot li a {
  color: var(--color-muted);
  text-decoration: none;
  display: block;
}
.versiot li a:hover {
  color: var(--color-text);
}
.versiot li.aktiivinen {
  font-weight: 600;
  color: var(--color-text);
}
.versiot .nykyinen {
  font-weight: 400;
  font-size: .78rem;
  color: var(--color-muted);
}

/* --- artikkeli-esikatselu ---
   Blockquote artikkelin alusta. */

.artikkeli-esikatselu {
  margin: 0 0 var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-low);
  border-left: var(--accent-stripe) solid var(--color-accent);
  border-radius: var(--radius-md);
}
.artikkeli-esikatselu p {
  margin: 0;
  line-height: 1.65;
}

/* --- artikkeli-metatiedot ---
   Sivun pohjan tekninen tietolohko (task #878, palaute #182b): artikkelin
   sisallon lyhennelma + paivaykset. Hillitty: tama on hakuteos, ei sisaltoa
   jota sivulle tullaan lukemaan. Ei omaa font-sizea — muted-koko tulee
   body.sivu-artikkeli -selektoriryhmasta (font-size-kynnys lukossa, 232). */

.artikkeli-metatiedot {
  margin: var(--space-5) 0 var(--space-4);
}
.artikkeli-metatiedot h3 {
  margin-bottom: var(--space-2);
  color: var(--color-muted);
}
.artikkeli-metatiedot dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-1) var(--space-3);
  margin: 0;
}
.artikkeli-metatiedot dt {
  color: var(--color-muted);
}
.artikkeli-metatiedot dd {
  margin: 0;
}

/* --- alkuperainen-linkki ---
   Linkki lahdeartikkeliin artikkelisivun meta-rivilla (task #878, palaute #183).
   Oli aiemmin muuta metaa PIENEMPI tekstilinkki, ja ulkopuolinen lukija ei
   loytanyt mista artikkelin paasee lukemaan. Nyt painikemainen pilleri: erottuu
   meta-rivin muista merkeista ilman etta se kilpailee sivun paa-CTA:n kanssa
   (ei gradienttia, ei accent-varia). */

.alkuperainen-linkki {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-primary-border);
  border-radius: var(--radius-full);
  background: var(--color-primary-bg);
  color: var(--color-primary);
  font-family: var(--font-label);
  font-weight: 700;
  white-space: nowrap;
}
.alkuperainen-linkki:hover {
  background: var(--color-primary);
  color: var(--color-text-inverse);
}

/* --- ai-varoitus ---
   Disclaimer teksti. #878: sisaltaa vain tekstia — sama lahdelinkki on
   korostettuna meta-rivilla (.alkuperainen-linkki), joten `a`-saanto poistettu. */

.ai-varoitus {
  font-size: .8rem;
  color: var(--color-muted);
  margin: 0;
  line-height: 1.4;
}

/* --- ai-merkinta ---
   AI-lapinakyvyysmerkinta (EU AI Act 50(4), task #783). Kolme varianttia:
   .leima  = generoidun tekstin yhteydessa, nakyvin
   .rivi   = aggregaattisivut (media/kirjoittaja/aihe), hillitty
   .tiivis = .merkki-tunnus listoissa ja tyokalupalkissa — EI omia saantoja,
             perii .merkki-luokan kokonaan. Perussaanto on siksi rajattu
             .leima+.rivi-variantteihin: .ai-merkinta-tason display/margin
             ylikirjoittaisi .merkki:n inline-flexin (sama spesifisyys, myohempi
             rivi voittaa) ja rikkoisi tunnuksen.
   Tekstikoot tulevat <small>- ja <strong>-elementeilta (Pico) — ei omia
   font-size-saantoja. */

.ai-merkinta.leima,
.ai-merkinta.rivi {
  color: var(--color-muted);
  line-height: 1.45;
}
.ai-merkinta.leima {
  display: block;
  margin: 0 0 var(--space-4);
  padding: var(--space-3);
  border-left: var(--accent-stripe) solid var(--color-primary);
  border-radius: var(--radius-md);
  background: var(--color-surface-low);
}
.ai-merkinta.leima strong { color: var(--color-text); }
.ai-merkinta.leima p { margin: var(--space-1) 0 0; }
.ai-merkinta-meta,
.ai-merkinta-linkki { display: block; margin-top: var(--space-2); }
/* Ylamarginaali (palaute #159, 14.8.2026): rivi-variantti renderoidaan
   sisallon JALKEEN (media/aihe/kirjoittaja: artikkelilistan alle), joten
   pelkka alamarginaali jatti merkinnan kiinni ylapuoliseen listaan.
   HUOM: (0,2,0) voittaa .mt-*-utilityn (0,1,0) — BriefingService antaa
   riville `mt-md`:n joka jaa nyt vaikutuksettomaksi (1rem -> 2rem). Ei
   rikkoudu: merkinta tarvitsee siella vain ilmaa tekstin ja itsensa valiin. */
.ai-merkinta.rivi { margin: var(--space-5) 0 var(--space-4); }
.ai-merkinta.leima a,
.ai-merkinta.rivi a {
  color: var(--color-muted);
  text-decoration: underline;
}
/* EU:n virallinen AI-merkintakuvake (#879). Yksi saanto kattaa kaikki kolme
   varianttia -- kuvake on osa merkintaa, ei varianttikohtainen koriste.
   flex: none   koska tiivis-variantti on .merkki (inline-flex): ilman tata
                kuvake litistyy ahtaassa sivuotsikon actions-slotissa.
   vertical-align koska leima ja rivi ovat tekstivirtaa (ei flexia), jolloin
                baseline jattaisi kuvakkeen liian ylos.
   Mitta em-yksikoissa: kuvake skaalautuu ymparoivan tekstin mukana (tiivis
   .75rem, leima/rivi perustekstin koko) ilman omaa font-size-saantoa. */
.ai-merkinta .ikoni {
  width: 1.15em;
  height: 1.15em;
  margin-right: 0.35em;
  vertical-align: -0.2em;
  flex: none;
}

/* --- nakokulma-banneri ---
   Artikkelin nakokulma -kortti accent-raidalla. */

.nakokulma-banneri {
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg);
  border-radius: var(--radius-lg);
  border-left: var(--accent-stripe) solid var(--color-primary);
  box-shadow: var(--shadow-sm);
  margin-bottom: var(--space-5);
}
.nakokulma-banneri h3 {
  font-size: 1rem;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}

/* --- valikot + valilehti ---
   Tab-navigaatio ja -paneelit. Kaytetaan legacy-tyylejä. */

.valikot {
  border-bottom: 2px solid var(--color-border);
  display: flex;
  gap: 0;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.valikot::-webkit-scrollbar { display: none; }
.valikkopainike {
  padding: var(--space-2) var(--space-3);
  border: none;
  background: none;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: .875rem;
  color: var(--color-text);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  white-space: nowrap;
  transition: all var(--transition-fast);
}
.valikkopainike:hover {
  color: var(--color-primary);
  background: var(--color-surface-low);
}

/* POISTETTU 29.8.2026 (#1117, palaute #260): `.valikkopainike.korostettu` + sen
   kolme tilahaaraa (:hover, [aria-selected="true"], .lukittu). Ainoa kayttaja
   oli artikkelisivun briefing-valilehti, joka lakkasi olemasta valilehti ja on
   nyt inline-haitari. Korostus EI kadonnut vaan siirtyi: ks. § todellisuus-
   tarkistus (~2000 rivia ylempana), joka lainaa saman `.painike.ensisij`-parin
   (navy gradient + inverse-teksti). Sinne siirtyi myos syy jonka takia
   sisaelementeille tarvitaan omat varihaarat.
   Jos korostettu valilehti joskus palaa, huomaa mika teki tasta hankalan:
   `.korostettu` ja `[aria-selected="true"]` ovat molemmat (0,2,0), joten
   lahdejarjestys ratkaisi — variantin oli pakko olla ENNEN tilasaantoja, eika
   arvoja siksi voinut jakaa selektoriryhmalla `.painike`-lohkon kanssa. */

.valikkopainike[aria-selected="true"] {
  color: var(--color-primary);
  border-bottom-color: var(--color-accent);
  font-weight: 600;
}
.valikkopainike.lukittu {
  opacity: 0.5;
  cursor: default;
  color: var(--color-muted);
}
.valikkopainike.lukittu:hover {
  opacity: 0.65;
  background: transparent;
}
@media (min-width: 641px) {
  .valikkopainike { padding: var(--space-2) var(--space-4); font-size: .9375rem; }
}

/* --- premium-ilmoitus --- */

.premium-ilmoitus {
  position: relative;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  margin-top: var(--space-2);
  max-width: 360px;
  box-shadow: var(--shadow-md);
  z-index: 10;
}
.premium-ilmoitus p {
  margin: 0 0 var(--space-2) 0;
  font-size: .9rem;
}

/* --- pisteet-lohko ---
   Iso pisteennaytto (vinouma, JSN). */

.pisteet-lohko {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.pisteet-lohko .arvo {
  font-size: 3rem;
  font-weight: 700;
  font-family: var(--font-body);
  line-height: 1;
}
/* Pisteet-lohkon ison numeron varivariantti — sama pattern kuin .mittarikortti.
   Resetoi .merkki-basen padding/radius/background, jotta numero pysyy isona
   ilman pillerimaista taustaa. Vari tulee bias/jsn-luokasta presenter-tasolla. */
.pisteet-lohko .arvo.merkki {
  display: inline-block;
  padding: 0;
  border-radius: 0;
  background: transparent;
  white-space: normal;
}
.pisteet-lohko .arvo.merkki.punainen  { color: var(--color-bias-high); }
.pisteet-lohko .arvo.merkki.keltainen { color: var(--color-bias-medium); }
.pisteet-lohko .arvo.merkki.vihrea    { color: var(--color-bias-low); }
.pisteet-lohko .otsikko {
  font-size: .875rem;
  color: var(--color-muted);
}

/* --- kehystys ---
   Poliittinen kehystys -lohko analyysivaliohjeessa. */

.kehystys .otsikko {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* --- kortti.kritisoitu / kortti.puolustettu ---
   Entiteettikorttien varivariantit. Otsikkovari kantaa tilan
   semantiikan (kritiikki = punainen, puolustus = vihrea). */

.kortti.kritisoitu { background: var(--color-bias-high-subtle); border-color: var(--color-bias-high-border); }
.kortti.kritisoitu h4 { color: var(--color-danger); }
.kortti.puolustettu { background: var(--color-bias-low-subtle); border-color: var(--color-bias-low-border); }
.kortti.puolustettu h4 { color: var(--color-success); }

/* --- propaganda-rivi ---
   Yksittainen propagandatekniikka-rivi. */

.propaganda-rivi {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-1);
}
.propaganda-rivi.aktiivinen {
  background: var(--color-rivi-aktiivinen-bg);
}
.propaganda-kuvaus {
  padding: 0 var(--space-3) var(--space-2);
}

/* --- vakavuus ---
   Severity badge propagandariville. */

.vakavuus {
  font-size: .65rem;
  padding: 0.1em 0.4em;
  border-radius: var(--radius-full);
  font-weight: 600;
  text-transform: uppercase;
  background: var(--color-surface-mid);
  color: var(--color-muted);
}
.vakavuus.severity-none,
.vakavuus.none {
  background: var(--color-surface-mid);
  color: var(--color-muted);
}
.vakavuus.severity-mild,
.vakavuus.mild {
  background: var(--color-vakavuus-mild-bg);
  color: var(--color-vakavuus-mild-text);
}
.vakavuus.severity-moderate,
.vakavuus.moderate {
  background: var(--color-vakavuus-moderate-bg);
  color: var(--color-vakavuus-moderate-text);
}
.vakavuus.severity-severe,
.vakavuus.severe {
  background: var(--color-vakavuus-severe-bg);
  color: var(--color-vakavuus-severe-text);
}

/* --- mockery-osio ---
   Artikkelisivun "Suora pilkka" -osio (task #392, schema v4, 5.5.2026).
   Listaa analysis.mockery[]-instanssit (sanatarkat pilkkasitaatit). */

.mockery-osio {
  margin-bottom: var(--space-4);
}
.mockery-osio .kuvaus {
  color: var(--color-muted);
  margin: 0 0 var(--space-3);
}
.mockery-lista {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* Pico-luodin vaimennus <li>-tasolla (vahti: --group=frontend). */
.mockery-lista li { list-style: none; }
.mockery-rivi {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.mockery-kohde {
  color: var(--color-text);
}
.mockery-sitaatti {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--color-border);
  background: var(--color-surface-mid);
  font-style: italic;
  line-height: 1.5;
}

/* --- otsikkoanalyysi ---
   Artikkelisivun bias-tabin "Otsikkoanalyysi"-osio (task #405, schema v5, 9.5.2026).
   Esittaa otsikon kolme nakokulmaa: factualAlignment (otsikko vs. sisalto),
   clickbaitScore (klikkihoukutus 0-100), polarizationTone (sananvalintojen lataus).
   Yhden silmaytyksen UX: jokainen rivi otsikko + score-merkki + kuvaus. */

.otsikkoanalyysi {
  margin-bottom: var(--space-4);
}
.otsikkoanalyysi-rivi {
  margin-bottom: var(--space-3);
}
.otsikkoanalyysi-rivi strong {
  margin-right: var(--space-2);
}
.otsikkoanalyysi-rivi .merkki {
  vertical-align: baseline;
}
.otsikkoanalyysi-kuvaus {
  margin: var(--space-1) 0 0;
  color: var(--color-muted);
  line-height: var(--line-height-base);
}

/* --- edistymispalkki ---
   Progress bar. (Omaperaisyys-osio siirtyi score-badgeen task #547 —
   ei kayttopaikkoja talla hetkella, sailytetty score view=bar -velkaa
   varten, ks. spec/komponentit.md § score.) */

.edistymispalkki {
  width: 100%;
  height: 8px;
  background: var(--color-surface-mid);
  border-radius: var(--radius-full);
  overflow: hidden;
}
.edistymispalkki .taytto {
  height: 100%;
  background: linear-gradient(90deg, var(--color-primary), var(--color-accent));
  border-radius: var(--radius-full);
  transition: width 0.4s ease;
}
/* --- rivien-valista ---
   Kansalaisen nakokulma: rakenteiset faktat. Scoping-namespace (ei oma kortti):
   sisalto istuu haitari-slotin .haitari-sisalto-kontainerissa, joten oma
   surface-low-tausta + margin poistettu (#11). Ala-selektorit maarittavat
   osiokohtaisen esitystavan (rivi-borderit, uppercase-otsikot). */

.rivien-valista h3 {
  font-size: 1.1rem;
  font-weight: 700;
  margin-bottom: var(--space-3);
}
.rivien-valista .rivi {
  display: block;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}
.rivien-valista .rivi:last-child { border-bottom: none; }
.rivien-valista .otsikko {
  display: block;
  font-size: .75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-muted);
  margin-bottom: var(--space-1);
}
.rivien-valista .arvo {
  display: block;
  font-weight: 400;
  font-size: .9rem;
  color: var(--color-text);
  text-align: left;
  line-height: 1.5;
}
.rivien-valista .yhteenveto {
  font-size: .9rem;
  color: var(--color-muted);
  margin-top: var(--space-3);
  margin-bottom: 0;
  line-height: 1.6;
}
/* v12: strukturoidut havainnot (presupposition/agency/voiceDistribution).
   Kukin havainto omalla rivillaan; sitaatti (.muted) description:n alla. */
.rivien-valista .havainto {
  display: block;
  margin-bottom: var(--space-2);
}
.rivien-valista .havainto:last-child { margin-bottom: 0; }
.rivien-valista .havainto .muted {
  display: block;
  margin-top: var(--space-1);
  font-style: italic;
}
/* v12: aiSpeculation — erottuva lohko + leima. Korostaa että kyseessä on
   tekoälyn tulkinta (accent-reuna surface-hierarkian sijaan, koska tämä on
   semanttisesti eri luonteinen kuin mitatut havainnot). */
.rivien-valista .ai-havainto {
  margin-top: var(--space-4);
  padding: var(--space-3);
  background: var(--color-surface-low);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-md);
}
.rivien-valista .ai-havainto .leima {
  display: inline-block;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-accent);
  margin-bottom: var(--space-1);
}
.rivien-valista .ai-havainto .sisalto {
  color: var(--color-text);
  line-height: 1.6;
  margin: 0 0 var(--space-1) 0;
}
.rivien-valista .ai-havainto .muted {
  margin: 0;
  font-style: italic;
}

/* --- hakutunnisteet ---
   Aiheesta muualla -tabin hakukomponentti. */

.hakutunnisteet {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
.hakutunniste {
  display: inline-flex;
  align-items: center;
  gap: 0.25em;
  padding: 0.3em 0.8em;
  border-radius: var(--radius-full);
  font-size: .8rem;
  font-weight: 500;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  color: var(--color-text);
  cursor: pointer;
  transition: all var(--transition-fast);
}
.hakutunniste:hover {
  border-color: var(--color-primary);
  background: var(--color-surface-mid);
}
.hakutunniste.aktiivinen {
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border-color: var(--color-primary);
}
.hakutunniste.aktiivinen:hover {
  background: var(--color-primary-light);
  border-color: var(--color-primary-light);
}
.hakutunniste.liittyva {
  border-style: dashed;
}
.hakutunniste.liittyva.aktiivinen {
  border-style: solid;
}
.hakutunniste .valinta {
  font-size: .7em;
  margin-left: .2em;
}

/* --- hakusyote ---
   Vapaan hakutermin tekstikentta "Aiheesta muualla" -paneelissa
   (Views/partials/article-osio-aiheesta-muualla.php). Sisarpari on
   .hakuvalitsin alla — samaa paneelia, sama nimeamislinja (haku + elementti).

   Nimetty uudelleen #744:ssa: luokka oli `.hakukentta`, joka tormasi kehyksen
   hakulomake-komponenttiin (#706). Sama nimi tarkoitti seka `<form>`ia etta
   `<input>`ia, joten nama input-tyylit valuivat lomakkeen kaareelle: tausta,
   padding ja border nakyivat lomakkeessa ja veivat ylapalkin kaistan niin
   etta lahetysnappi rivittyi. */

.hakusyote {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  font-size: .875rem;
  background: var(--color-bg);
  color: var(--color-text);
  margin-bottom: 0;
}
.hakusyote:focus {
  border-color: var(--color-primary);
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}

/* --- hakuvalitsin --- */

.hakuvalitsin {
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: .8rem;
  background: var(--color-bg);
  color: var(--color-text);
  margin-bottom: 0;
}

/* --- lahde-ruudukko --- */

.lahde-ruudukko {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-1);
}
.lahde {
  display: flex;
  align-items: center;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: .75rem;
  cursor: pointer;
  transition: all var(--transition-fast);
  user-select: none;
}
.lahde input[type="checkbox"] { display: none; }
.lahde:hover {
  border-color: var(--color-primary);
  background: var(--color-surface-mid);
}
.lahde.aktiivinen {
  background: var(--color-surface-low);
  border-color: var(--color-primary);
  color: var(--color-primary);
  font-weight: 600;
}

/* Tabletti: 3 saraketta, desktop: 4 saraketta */
@media (min-width: 641px) {
  .lahde-ruudukko { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 1025px) {
  .lahde-ruudukko { grid-template-columns: repeat(4, 1fr); }
}

/* --- hakutulokset --- */

.hakutulokset {
  margin-top: var(--space-3);
}
.hakutulos {
  transition: box-shadow var(--transition-fast);
}
.hakutulos .otsikkolinkki { font-weight: 600; }

/* --- kontekstilinkit / kontekstilinkki ---
   CTA-linkit artikkelisivun alaosassa. */

.kontekstilinkit {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-5);
  margin-bottom: var(--space-4);
}
.kontekstilinkki {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-3);
  border-radius: var(--radius-lg);
  font-weight: 600;
  font-size: .85rem;
  text-decoration: none;
  color: var(--color-primary);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  transition: background var(--transition-fast), border-color var(--transition-fast);
}
.kontekstilinkki:hover {
  background: var(--color-surface-high);
  border-color: var(--color-primary);
}
.kontekstilinkki.ensisijainen {
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border-color: var(--color-primary);
}
.kontekstilinkki.ensisijainen:hover {
  background: var(--color-primary-light);
  color: var(--color-text-inverse);
}
.kontekstilinkki .nuoli {
  font-size: 1.2rem;
  flex-shrink: 0;
  margin-left: var(--space-3);
}
@media (min-width: 769px) {
  .kontekstilinkki { font-size: .9rem; padding: var(--space-3) var(--space-4); }
}

/* --- artikkeli-tagit ---
   Topic-tagit artikkelisivun alaosassa. */

.artikkeli-tagit {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}

/* --- lahteen-asiasanat ---
   Lahdemedian omat asiasanat/osastot (scrape_metadata, #611) analyysitagien
   alla. Otsikko on <small>-elementti -- perii .75rem pohjasaannosta (osio 2),
   EI uutta font-size-deklaraatiota (H2-css-2-mittari 229/232). Pillit
   renderoi tagit-komponentin view=plain neutraalilla .merkki-perusluokalla
   (erottuu .merkki.tumma-analyysitageista). */
.lahteen-asiasanat { margin-bottom: var(--space-4); }
.lahteen-asiasanat-otsikko {
  display: block;
  font-weight: 600;
  color: var(--color-text);
  margin-bottom: var(--space-2);
}

/* --- pikakutsu ---
   Mini-esittely / CTA-osio artikkelisivun lopussa. */

.pikakutsu {
  background: var(--color-primary-subtle);
  padding: var(--space-4);
  border-radius: var(--radius-lg);
  border: 2px solid var(--color-primary-border);
  margin-bottom: var(--space-5);
  text-align: center;
}
.pikakutsu h3 {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--color-primary);
  margin-bottom: var(--space-2);
}
.pikakutsu p {
  font-size: .85rem;
  color: var(--color-muted);
  margin-bottom: var(--space-3);
  line-height: 1.6;
}
.pikakutsu .painike {
  display: inline-block;
  width: 100%;
  max-width: none;
}
@media (min-width: 769px) {
  .pikakutsu .painike { max-width: 300px; }
}

/* --- painike (geneerinen) ---
   Kaytto: .painike, .painike.ensisij, .painike.maksullinen, .painike.hero
   Ylapalkki ylikirjoittaa omaan kontekstiinsa. */

.painike {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  font-size: .9375rem;
  font-family: var(--font-body);
  font-weight: 600;
  cursor: pointer;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  color: var(--color-text);
  text-decoration: none;
  transition: all var(--transition-fast);
  line-height: 1.4;
}
.painike:hover {
  background: var(--color-surface);
  border-color: var(--color-outline);
}
/* .maksullinen jakaa .ensisij:n sinisen tarkoituksella (Teemun paatos, #747):
   krediittia kuluttava toiminto nayttaa paatoiminnolta, jotta hinta ja
   painoarvo lukevat samassa napissa. Deklaraatiot jaetaan selektoriryhmalla
   eika kopioida — nain varit eivat paase eroon toisistaan.

   Miksi oma semanttinen luokka eika pelkka .ensisij:
     1) grep loytaa yhdella haulla kaikki napit joiden takana on veloitus,
     2) kun maksullisuudelle joskus lisataan oma tunniste (krediitti-ikoni,
        reunus), se lisataan tahan yhteen sarjaan eika 4+ nappiin erikseen.

   Luokka toimii yksinaan: class="painike maksullinen" riittaa, .ensisij ei
   ole ehto. Kokoluokat (.pieni, .xpieni, .w-full, .hero) komposoituvat
   normaalisti — ne saatavat vain mittoja, eivat vareja. */
.painike.ensisij,
.painike.maksullinen {
  background: var(--gradient-primary);
  color: var(--color-text-inverse);
  border-color: var(--color-primary);
}
.painike.ensisij:hover,
.painike.maksullinen:hover {
  box-shadow: var(--shadow-sm);
}
.painike.hero {
  padding: var(--space-3) var(--space-5);
  font-size: 1.0625rem;
  background: var(--gradient-primary);
  color: var(--color-text-inverse);
  border-color: var(--color-primary);
  border-radius: var(--radius-lg);
}
.painike.hero:hover {
  box-shadow: var(--shadow-md);
}

/* --- painike-variantit (ghost, accent, vaara, pieni, xpieni) --- */

.painike.ghost {
  background: transparent;
  color: var(--color-text);
  border-color: var(--color-border);
}
.painike.ghost:hover {
  background: var(--color-surface);
}
.painike.accent {
  background: var(--color-accent);
  color: var(--color-text-inverse);
  border-color: var(--color-accent);
}
.painike.accent:hover {
  background: var(--color-accent-light);
}
.painike.vaara {
  background: var(--color-danger);
  color: var(--color-text-inverse);
  border-color: var(--color-danger);
}
.painike.vaara:hover {
  opacity: 0.9;
}
/* Google-kirjautuminen (#1379). Varianttina .painike-lohkossa eika
   kirjautumis-modaalin sisalla, jotta kaikki .painike.X -variantit pysyvat
   luettavissa yhtena kokonaisuutena (frontend-css.md § Yhden komponentin
   osion rakenne).

   Hex-arvot ovat Googlen brandivareja (valkoinen pohja, #747775 reunus,
   #1f1f1f teksti) — frontend-css.md § "Mita ei tehda" sallii ulkoiset
   brandivarit hexina komponenttilohkossa. ALA vaihda naita tokeneiksi:
   nappi ei saa muuttua palvelun teeman mukana, koska se on Googlen tunnus.
   Tummaa teemaa ei ole (prefers-color-scheme 0 osumaa), joten valkoinen
   pohja on turvallinen.

   Ensisijaisuus tulee JARJESTYKSESTA (nappi on ensimmainen, magic link
   erottimen alla) — ei siita etta tama olisi visuaalisesti aanekkaampi kuin
   .ensisij. Sahkopostilomakkeen submit PYSYY .ensisij:na: #824/#648 nostivat
   sen ghostista kolmannen saman vikaluokan toiston jalkeen. */
.painike.google {
  background: #fff;
  color: #1f1f1f;
  border-color: #747775;
}
.painike.google:hover {
  background: #f8f9fa;
  border-color: #747775;
  box-shadow: var(--shadow-sm);
}
.painike.google .ikoni {
  width: 1.125em;
  height: 1.125em;
}
.painike.pieni {
  font-size: .8rem;
  padding: var(--space-1) var(--space-2);
}
.painike.xpieni {
  font-size: .75rem;
  padding: 0.15rem 0.5rem;
  line-height: 1.4;
}

/* --- ilmoitus-variantit (warning, danger) --- */

.ilmoitus.warning {
  background: var(--color-bias-medium-bg);
  color: var(--color-bias-medium-text);
}
.ilmoitus.danger {
  background: var(--color-bias-high-bg);
  color: var(--color-bias-high-text);
}

/* --- askel-numero ---
   Iso numeroindikaattori (01, 02...) palvelusivun vaihelistassa. */

.askel-numero {
  font-size: 2rem;
  font-weight: 700;
  color: var(--color-primary);
  font-family: var(--font-editorial);
  margin-bottom: var(--space-2);
}

/* --- kortti (geneerinen) ---
   Uudelleennimetty card -> kortti. */

.kortti {
  position: relative;
  display: block;
  width: 100%;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
}
/* Accent-stripe varianttina (kun pitää korostaa kortti). Spec/frontend-css.md
   periaate 3 — variantti pisteen kautta. Aiempi oletus rikkoi etusivun. */
.kortti.korostettu {
  border-left: var(--accent-stripe) solid var(--color-primary);
}
.kortti h2 { margin-top: 0; font-size: 1.125rem; }
.kortti h3 { font-size: 1rem; }
.kortti img { border-radius: var(--radius-sm); }
.kortti .meta { font-size: .875rem; color: var(--color-muted); }
.kortti.fancy { border-color: var(--color-accent); box-shadow: var(--shadow-md); }
.kortti.fancy h2 { color: var(--color-accent); }
.kortti.compact { padding: var(--space-2); }
@media (min-width: 768px) {
  .kortti { padding: var(--space-4); }
}

/* --- profiili ---
   Profiilisivun layout + Osta-kortit grid + iso krediittisaldo. */

.profiili.kapea { max-width: 760px; margin: 0 auto; }

.profiili-saldo {
  font-size: 3rem;
  font-weight: 700;
  font-family: var(--font-editorial);
  color: var(--color-primary);
  line-height: 1;
  margin: var(--space-2) 0;
}
.credit-positive { color: var(--color-success); font-weight: 600; }
.credit-negative { color: var(--color-danger); font-weight: 600; }

/* Osta-kortit: responsiivinen grid — auto-fit, 240px minimum leveys.
   1 col mobiili, 2-3 col desktop riippuen viewport-leveydesta. */
.osta-kortit {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-4);
}

/* Variantti: tiivis — kapeat kontit (krediitti-modaali, sm-modaalin sisältö ~300px).
   Pienempi minimisarake → paketit rinnakkain jo modaalissa, kapeammalla pinoutuvat.
   Korttipinta (focal paper) erottaa paketit modaalin glass-taustasta ilman bordereita. */
.osta-kortit.tiivis {
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: var(--space-3);
}
.osta-kortit.tiivis > div {
  background: var(--color-bg);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  box-shadow: var(--shadow-sm);
}

/* Profiilisivun lomakepainikkeet: Pico tekee submit-napeista 100% widthin
   form:n ainoana lapsena — overridataan inline-auto. */
.profiili form button.painike,
.profiili form input[type="submit"].painike { width: auto; }

/* --- krediittiloki-taulukko ---
   Profiilisivun kulutushistoria. Mobile-first: rivit taittuvat allekkain
   (label + arvo per kenttä) jotta kapealla ruudulla ei tule vaakascrollia.
   Desktopilla (≥768px) palautuu tabulaariseksi. Sama partial renderöidään
   myös fragment-endpointissa (ProfileController::creditsFragment) — sama
   markup molemmissa, sopimus säilyy. */

/* Mobiili (perustyyli) — lista-näkymä.

   ⚠️ PICO OMISTAA `td,th`:N TAUSTAN JA VIIVAN (palaute #139, 11.8.2026).
   `td,th { background-color: var(--pico-background-color); border-bottom:
   1px solid var(--pico-table-border-color) }` on (0,0,1) mutta se osuu
   jokaiseen soluun, ja lista-näkymässä solu on oma lohkonsa. Lopputulos
   mobiilissa: jokainen SOLU piirtyi valkoisena laatikkona haitarin
   `--color-surface`-taustaa vasten, ja koska oma sääntö kavensi paddingin
   arvoon `var(--space-1) 0`, laatikot näyttivät vaakasuunnassa
   paddingittomilta. Teemun sanoin: "valkoiset laatikot joka entryssä,
   ilman paddingiä".

   Korjaus on Teemun vaihtoehto: pinta siirtyy SOLUSTA RIVILLE, jolloin yksi
   tapahtuma = yksi kortti jolla on oikea padding. Solut ovat läpinäkyviä ja
   viivattomia — rivien erottelu tehdään pintaerolla, ei 1px-viivalla
   (No-Line Rule, frontend-css.md § 9). Desktop-lohko palauttaa tabulaarisen
   ilmeen sellaisenaan. */
.krediittiloki-taulukko { display: block; width: 100%; }
.krediittiloki-taulukko thead { display: none; }
.krediittiloki-taulukko tbody { display: block; }
.krediittiloki-taulukko tr {
  display: block;
  padding: var(--space-3);
  margin-bottom: var(--space-2);
  background: var(--color-bg);
  border-radius: var(--radius-md);
  border-bottom: none;
}
.krediittiloki-taulukko tr:last-child { margin-bottom: 0; }
.krediittiloki-taulukko td {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  background: none;    /* Picon solutausta pois — rivi kantaa pinnan */
  border-bottom: none; /* Picon solukohtainen viiva pois (No-Line) */
  text-align: left;  /* override inline style="text-align:right" mobiilissa */
  /* Pitkät yhtenäiset stringit (esim. Stripe cs_live_… session-ID) rikotaan
     mihin kohtaan tahansa jotta ne eivät työnnä solua yli ruudun mobiilissa.
     `anywhere` sallii rikkomisen myös sanan sisällä — `break-word` ei riitä
     koska Stripe-ID:ssä ei ole sanavälejä. */
  overflow-wrap: anywhere;
}
.krediittiloki-taulukko td::before {
  content: attr(data-otsikko);
  font-weight: 600;
  color: var(--color-muted);
  flex-shrink: 0;
}

/* Desktop — palautuu tabulaariseksi.
   Pinta ja rivinerotin palaavat SOLUUN (kuten Picolla), koska taulukossa
   rivi ei ole kortti vaan rivi. Mobiilin korttitila puretaan eksplisiittisesti
   (tausta, margin, pyöristys) — muuten `display: table-row` perisi ne. */
@media (min-width: 768px) {
  .krediittiloki-taulukko { display: table; }
  .krediittiloki-taulukko thead { display: table-header-group; }
  .krediittiloki-taulukko tbody { display: table-row-group; }
  .krediittiloki-taulukko tr {
    display: table-row;
    padding: 0;
    margin-bottom: 0;
    background: none;
    border-radius: 0;
    border-bottom: none;
  }
  .krediittiloki-taulukko td {
    display: table-cell;
    padding: var(--space-2);
    background: var(--color-bg);
    border-bottom: 1px solid var(--color-border);
    text-align: inherit;  /* palauta inline-tyyli (right-align Krediitit) */
  }
  .krediittiloki-taulukko td::before { content: none; }
}

/* --- admin: suppilo-palkki ---
   Konversiosuppilon bar analytics-sivulla. */

.suppilo-palkki-wrap {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-1) 0;
}
.suppilo-palkki {
  height: 1.4rem;
  background: var(--color-primary);
  border-radius: var(--radius-md);
  min-width: 2px;
  transition: width 0.3s ease;
}
.suppilo-palkki-label {
  white-space: nowrap;
  font-size: .85rem;
}
.suppilo-pudotus {
  color: var(--color-muted);
  font-size: .8rem;
  margin-left: var(--space-1);
}

/* --- admin: mod-kortti ---
   Moderointikortit moderation-sivulla. */

.mod-kortti {
  margin-bottom: var(--space-3);
}
.mod-kortti header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.mod-meta {
  font-size: .85rem;
  color: var(--color-muted);
}
.mod-syy {
  margin: var(--space-2) 0;
  padding: var(--space-2);
  background: var(--color-surface);
  border-left: 3px solid var(--color-primary);
  border-radius: var(--radius-md);
}
.mod-toiminnot {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.mod-toiminnot button {
  margin-bottom: 0;
  padding: var(--space-1) var(--space-3);
  font-size: .85rem;
}

/* --- admin: artikkeli-linkit ---
   Käytetään moderation-näkymässä (admin/moderation.php) ja muissa admin-tauluissa
   joissa pitää linkata sisäiseen analyysiin (otsikko/ID) tai alkuperäiseen
   ulkoiseen artikkeliin (URL ↗). #402, 9.5.2026.

   Otsikko leikataan CSS:llä (text-overflow: ellipsis) — ei kovakoodausta PHP:ssä. */

.artikkeli-id {
  color: var(--color-muted);
  font-family: monospace;
  font-size: .85rem;
  margin-right: var(--space-1);
}
a.artikkeli-id {
  text-decoration: none;
}
a.artikkeli-id:hover {
  color: var(--color-primary);
  text-decoration: underline;
}
.artikkeli-otsikko {
  display: inline-block;
  max-width: 40ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}
.artikkeli-ulkoinen {
  display: inline-block;
  margin-left: var(--space-1);
  text-decoration: none;
}
.artikkeli-ulkoinen:hover {
  text-decoration: underline;
}

/* --- admin: badge-variantit (moderointi) --- */

.merkki.odottaa {
  display: inline-block;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--color-odottaa-bg);
  color: var(--color-odottaa-text);
  font-size: .75rem;
  font-weight: 600;
}
.merkki.hyvaksytty {
  display: inline-block;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--color-hyvaksytty-bg);
  color: var(--color-hyvaksytty-text);
  font-size: .75rem;
  font-weight: 600;
}
.merkki.hylatty {
  display: inline-block;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--color-hylatty-bg);
  color: var(--color-hylatty-text);
  font-size: .75rem;
  font-weight: 600;
}

/* --- toast ---
   Toast-ilmoitus fixed-positiossa (moderointi, admin). */

.toast {
  position: fixed;
  bottom: var(--space-3);
  right: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  /* Oletustausta: variantiton toast (showToast ilman success/error-luokkaa)
     oli lapinakyva → lukukelvoton paallekkaisteksti kelluvan analyysijonon
     paalla (bugi 10.6.2026). Tumma neutraali + floating-varjo. */
  background: var(--color-text);
  box-shadow: var(--shadow-float);
  color: var(--color-text-inverse);
  font-weight: 600;
  z-index: 1000;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.toast.show { opacity: 1; }
.toast.success { background: var(--color-success); }
.toast.error { background: var(--color-danger); }

/* --- takaisin-ylos ---
   Kelluva pyorea nappi sivun alkuun (palaute #433). Komponentti:
   kehys/Views/components/takaisin-ylos.php, nakyvyys: /js/takaisin-ylos.js
   (hidden kunnes vieritetty > 3 nayton korkeutta).
   Sijainti: kelluvan .analyysijono-palkin YLAPUOLELLA. Mobiilissa jono on koko
   levyinen palkki alareunassa, joten nappi nostetaan jonon bottom-arvo +
   otsikkopalkin korkeus (~--space-6) + rako. z-index 40 < jonon 50: avattu
   jono peittaa napin eika toisin pain. */
.takaisin-ylos {
  position: fixed;
  right: var(--space-2);
  bottom: calc(max(env(safe-area-inset-bottom, 0px) + var(--space-4), var(--space-5)) + var(--space-6) + var(--space-2));
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--space-6);
  height: var(--space-6);
  margin: 0;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-text-inverse);
  box-shadow: var(--shadow-float);
  font-size: 1.25rem;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.takaisin-ylos:hover { background: var(--color-primary-light); }
.takaisin-ylos:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
/* display:flex kumoaisi hidden-attribuutin ilman tata */
.takaisin-ylos[hidden] { display: none; }
@media (min-width: 481px) {
  .takaisin-ylos {
    right: var(--space-4);
    bottom: calc(var(--space-4) + var(--space-6) + var(--space-2));
  }
}

/* --- sisaltosivu ---
   Staattisen sisaltosivun kapea tekstipalsta (tietosuoja, kayttoehdot,
   miten-tama-on-tehty, docs/api). Komponentti:
   kehys/Views/components/sisaltosivu.php.

   Korvasi .sisalto-kapea-luokan (#828, palaute #167): se nollasi Picon
   <article>-pystypaddingin (`padding: 0 var(--space-3)`), jolloin h1 liimautui
   kortin ylareunaan ja viimeinen kappale alareunaan — molemmat mitattu 0 px
   tuotannosta. Valkoinen kortti + radius tulevat Picon <article>-saannosta;
   tama lohko antaa ilman ja rajaa rivinpituuden.

   Sijainti osiossa 6 (komponentit) eika 5 (sivurakenne): palikka ei oleta
   aidiltaan muuta kuin leveyden, samoin kuin .kortti. Sama paikka jossa
   .sisalto-kapea oli. */

.sisaltosivu {
  max-width: 720px;
  margin: 0 auto var(--space-5);
  padding: var(--space-4) var(--space-3);
}
/* Otsikkolohko: h1 + paivityspvm, erotus alas ghost-linella (sama kuvio kuin
   .sivuotsikko:lla). Kumoaa Picon `article>header`-saannon (0,0,2), jonka
   sectioning-tausta ja negatiiviset sivumarginaalit on mitoitettu Picon omaan
   paddingiin — ne eivat osu taman lohkon paddingiin vaan tyontaisivat
   otsikkopalkin reunojen yli. Oma selektori (0,1,1) voittaa lahdejarjestyksen
   lisaksi myos spesifisyydella, joten !importantia ei tarvita. */
.sisaltosivu > header {
  margin: 0 0 var(--space-4);
  padding: 0 0 var(--space-3);
  background: none;
  border-radius: 0;
  border-bottom: 1px solid var(--color-border);
}
/* Picon `article>header>*` nollaa lastensa margin-bottomin — palautetaan vali
   otsikon ja pvm-rivin valiin. */
.sisaltosivu > header h1 { margin-bottom: var(--space-2); }
/* Paivityspvm-rivi otsikon alla */
.sisaltosivu .paivamaara { font-size: 0.875rem; color: var(--color-muted); margin-bottom: 0; }

/* Desktop */
@media (min-width: 768px) {
  .sisaltosivu { padding: var(--space-5) var(--space-4); }
}

/* --- artikkeli-suodattimet --- */
.artikkeli-suodattimet { margin-bottom: var(--space-3); }
.artikkeli-suodattimet .suodatinpalkki {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: var(--space-3);
    flex-wrap: wrap;
}
.artikkeli-suodattimet .lajittelu { display: flex; flex-direction: column; gap: var(--space-1); min-width: 160px; }
.artikkeli-suodattimet .lajittelu select { margin-bottom: 0; }
.artikkeli-suodattimet .toiminnot { display: flex; gap: var(--space-1); align-items: flex-end; }
.artikkeli-suodattimet .otsikko {
    font-family: var(--font-label);
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--color-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}
.artikkeli-suodattimet .paneeli {
    display: flex;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-top: var(--space-2);
    padding: var(--space-3);
    background: var(--color-surface-low);
    border-radius: var(--radius-md);
}
.artikkeli-suodattimet .ryhma { display: flex; flex-direction: column; gap: var(--space-1); min-width: 140px; }
.artikkeli-suodattimet .ryhma select { margin-bottom: 0; }
.artikkeli-suodattimet .maara { margin-top: var(--space-1); }
.artikkeli-suodattimet .maara span { font-size: 0.875rem; color: var(--color-muted); } /* tulosten lukumaara */
.artikkeli-suodattimet .aktiivi-piste {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: var(--radius-full);
    background: var(--color-accent);
    margin-left: var(--space-1);
    vertical-align: middle;
}
/* Poliittinen nakokulma -suodatin (task #304 → #313).
   Sijaitsee paneelin sisalla "Suodattimet"-napin takana — sama paikka kuin
   vinouma + aihe (siirretty 30.4. UX-palautteen 29.4. kohta 4 perusteella).
   Kayttaa flex-basis: 100% jotta painikerivi saa oman rivinsa paneelin
   flex-row-layoutissa eika ahtaudu vinouma- ja aihe-ryhmien viereen. */
/* Suhde aiheeseen -suodatin (task #628) jakaa saman rakenteen ja tyylin —
   ryhmitelty selektori, ei rinnakkaista saantoblokkia. */
.artikkeli-suodattimet .paneeli .suodatin-poliittinen,
.artikkeli-suodattimet .paneeli .suodatin-aihesuhde {
    flex-basis: 100%;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.artikkeli-suodattimet .paneeli .suodatin-poliittinen .painikerivi,
.artikkeli-suodattimet .paneeli .suodatin-aihesuhde .painikerivi {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
}
.poliittinen-painike,
.aihesuhde-painike {
    display: inline-flex;
    align-items: center;
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-md);
    border: 1px solid var(--color-outline);
    background: var(--color-bg);
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: 0.8rem;
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition-fast);
}
.poliittinen-painike:hover,
.aihesuhde-painike:hover {
    background: var(--color-surface);
}
.poliittinen-painike.aktiivinen,
.aihesuhde-painike.aktiivinen {
    background: var(--color-surface-low);
    border-color: var(--color-text);
    font-weight: 600;
}
/* Aktiivitilan visuaalinen tunniste — politiikan vari vasemmassa reunassa.
   Kayttaa --color-poliittinen-* tokeneita (sama data = sama vari kuin
   drilldown-kaavioissa ja sidebar-jakaumassa). */
.poliittinen-painike.vasen.aktiivinen {
    border-color: var(--color-poliittinen-vasen);
    border-left: 4px solid var(--color-poliittinen-vasen);
}
.poliittinen-painike.keskus.aktiivinen {
    border-color: var(--color-poliittinen-keskusta);
    border-left: 4px solid var(--color-poliittinen-keskusta);
}
.poliittinen-painike.oikea.aktiivinen {
    border-color: var(--color-poliittinen-oikea);
    border-left: 4px solid var(--color-poliittinen-oikea);
}
.poliittinen-painike:focus-visible,
.aihesuhde-painike:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}

/* --- artikkeli-lista --- */
.artikkeli-lista .lataus,
.artikkeli-lista .virhe {
    text-align: center;
    padding: var(--space-4) 0;
}
.artikkeli-lista .lataus p,
.artikkeli-lista .virhe p { color: var(--color-muted); } /* muted-text korvaus; .tyhja-tila: perussaanto #790 */

/* --- sivutus --- */
/* `:not([hidden])` on speksin vakiintunut kuvio (ks. `.hero-haku > input:not([hidden])`
   ja `frontend-css.md § Picon lomakesaannot`): ilman sita oma `display: flex` kumoaisi
   selaimen `[hidden] { display: none }` -oletuksen, koska tekijan saanto voittaa
   selaimen oletuksen spesifisyydesta riippumatta. `sivutus`-komponentin `jsOnly`-tila
   piilottaa navin `hidden`-attribuutilla ja `sivutus-enhance.js` poistaa attribuutin
   hydratoidessaan — piilotus ei saa vuotaa auki tasta saannosta. Ei `!important`. */
.sivutus:not([hidden]) { display: flex; justify-content: center; align-items: center; gap: var(--space-2); margin: var(--space-5) 0; flex-wrap: wrap; }
.sivutus .sivu {
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    color: var(--color-muted);
    text-decoration: none;
    min-width: 1.75rem;
    font-size: .875rem;
    text-align: center;
    transition: background-color var(--transition-fast), color var(--transition-fast);
}
.sivutus .sivu:hover { background: var(--color-surface); color: var(--color-text); }
.sivutus .sivu.aktiivi { background: var(--color-primary); color: var(--color-text-inverse); font-weight: 600; }
.sivutus .sivu.disabloitu { opacity: 0.4; pointer-events: none; }
.sivutus .sivu.ellipsis { pointer-events: none; min-width: auto; padding: var(--space-2) var(--space-1); }
.sivutus-info { padding: var(--space-2) var(--space-3); color: var(--color-muted); }
.sivutus button:disabled { opacity: 0.5; cursor: not-allowed; }

/* --- valintalinkit (komponentti `valintalinkit`, kehys) ---
   Nakymatilan valinta linkkeina (esim. hakutulosanalyysin kate #822).
   Sukua `.sivutus`elle ulkoasultaan mutta ei sama asia: sivutus liikkuu
   joukon SISALLA, tama vaihtaa joukon MAARITELMAA. Ei omaa font-sizea —
   peritaan ympariston koko, jolloin komponentti sopii sekä leipatekstin
   etta osio-otsikon viereen ilman variantteja. */
.valintalinkit {
    display: flex;
    align-items: center;
    /* ALA POISTA: kumoaa Pico-CSS:n `nav { justify-content: space-between }`.
       Rivi nayttaa flexin oletusarvolta ja siksi turhalta, mutta ilman sita
       Pico levittaa vaihtoehdot rivin molempiin laitoihin (mitattu tuotannossa
       #822: 1368 px leveassa sailiossa vali oli 1040 px) — kaksi saman
       valinnan vaihtoehtoa lukeutuu silloin erillisiksi kontrolleiksi ja
       `gap` ei paase vaikuttamaan. */
    justify-content: flex-start;
    gap: var(--space-1);
    flex-wrap: wrap;
    margin: var(--space-2) 0 var(--space-4);
}
.valintalinkit .valinta {
    padding: var(--space-1) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    color: var(--color-muted);
    text-decoration: none;
    transition: background-color var(--transition-fast), color var(--transition-fast);
}
.valintalinkit a.valinta:hover { background: var(--color-surface); color: var(--color-text); }
.valintalinkit .valinta.aktiivinen {
    background: var(--color-primary);
    border-color: var(--color-primary);
    color: var(--color-text-inverse);
    font-weight: 600;
}

/* --- jako-painikkeet (komponentti `jako`, kehys) ---
   Yksi lohko koko sivustolle. `.jako-painikkeet` = view='painikkeet'
   (ikonirivi), pudotus-variantti kayttaa jaettuja .dropdown-tyyleja.
   Ylapalkin `.ylapalkki .jako` -saannot poistettu K1-templatet-6:ssa kun
   ylapalkin kuollut jakopalkki poistui. */
.jako-painikkeet {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.jako-painikkeet .otsikko {
    font-size: 0.8rem;
    color: var(--color-muted);
    font-weight: 500;
}
.jako-painike {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    background: var(--color-bg);
    color: var(--color-muted);
    cursor: pointer;
    transition: all var(--transition-fast);
    text-decoration: none;
    padding: 0;
}
.jako-painike:hover {
    border-color: var(--color-primary);
    color: var(--color-primary);
    background: var(--color-surface-low);
}
.jako-painike.facebook:hover { color: #1877f2; border-color: #1877f2; }
.jako-painike.x-some:hover { color: var(--color-text); border-color: var(--color-text); }
.jako-painike.linkedin:hover { color: #0a66c2; border-color: #0a66c2; }
.jako-painike.whatsapp:hover { color: #25d366; border-color: #25d366; }
.jako-painike.kopioi:hover { color: var(--color-primary); border-color: var(--color-primary); }

/* jako-painikkeet header-kontekstissa */
.otsikko-jako .jako-painikkeet {
    gap: var(--space-1);
    flex-wrap: nowrap;
}
.otsikko-jako .jako-painikkeet .otsikko { display: none; }
.otsikko-jako .jako-painike {
    width: 28px;
    height: 28px;
}
.otsikko-jako .jako-painike svg {
    width: 14px;
    height: 14px;
}

/* --- erotin (card-meta separator) --- */
.erotin { color: var(--color-border); }

/* --- porautumis-analytiikka ---
   Drilldown-sivujen grid + palkit + kaaviot.
   Käytetään: author, media, topic analytiikkaosiossa. */

.porautumis-ruudukko {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin-top: var(--space-3);
}
.porautumis-kortti {
  position: relative;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
}
.porautumis-kortti.levea {
  grid-column: 1 / -1;
}
.porautumis-kortti h3 {
  font-size: 0.95rem;
  font-weight: 600;
  margin-bottom: var(--space-2);
  color: var(--color-text);
}
/* Kortti ilman omaa otsikkoa (#1464): nostossa `nosto`n h2 on otsikko, joten
   komponentti jattaa h3:n pois. Vihjeen i-nappi on absoluuttisesti oikeassa
   ylakulmassa (`.porautumis-kortti > .vihje`), ja ilman h3:a se osuisi
   ensimmaisen rivin paalle — tila jonka h3 ennen antoi palautetaan
   sisallolle. Vain kun vihje on olemassa: ilman sita valia ei tarvita. */
.porautumis-kortti.ilman-otsikkoa > .vihje + * {
  margin-top: var(--space-3);
}
/* MOBILE-FIRST (#1180, palaute #286): perussaanto = kapea ruutu, jossa selite on
   OMALLA RIVILLAAN ja palkki saa koko leveyden. Rivien valin (--space-2, 8px) on
   oltava SUUREMPI kuin rivin sisainen vali (2px, `.porautumis-palkkirivi` alla),
   muuten lukija ei erota mika selite kuuluu mihin palkkiin. 480px palauttaa
   tiiviin --space-1:n, koska silloin rivi on taas yksi visuaalinen yksikko. */
.porautumis-palkit {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.porautumis-palkkirivi {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2px var(--space-2);
}
/* Selite = nimisarake, ja nimi joko luetaan tai sita ei ole. Perussaannon
   kiintea 100px + ellipsis katkaisi sen, ja sama vika mitattiin kolmesti
   itsenaisesti: #918 (aiheiden nimet), #971 (juttutyypit), palaute #204
   (360x674). Palaute #286 nimesi saman juurisyyn toisen puolen: "ovat niin
   kapeita ettei erot nay" — kiintea selitesarake syo palkin leveyden, joten
   katkeava nimi ja mittaamaton palkki ovat sama vika kahdesta suunnasta.
   Teemun ratkaisu: kapealla naytolla selite omalle rivilleen ja palkki
   taysleveana, leveilla naytoilla nykyinen rinnakkainen asettelu.

   MIKSI 480px EIKA 768/769: 1-sarakkeisessa kortissa rata = viewport − 32
   (container) − 32 (kortti) − selite − arvo − 2x gap, eli perussaannolla
   rata = V − 290. 360px:lla se on 130px ja `.juttutyyppien-laatu`lla
   (selite 130 + arvo 92) vain 58px. Luettava ~180px tayttyy vasta 470px:lla
   → raja on mitattu piste jossa rinnakkainen asettelu alkaa toimia, ei kopio
   olemassa olevasta breakpointista. Pinottuna sama 360px:n rata on 238px /
   196px. (Mitatut selitepituudet 2.9.2026: medianimet <= 21 merkkia,
   juttutyypit <= 18, aihetunnisteet ka. 10,8 ja >= 20 artikkelin aiheista
   pisimmat 29-32, aikajanan bucketit 4-7.)

   ⚠️ `flex: 0 0 100%` EIKA `width: 100%`: kolme varianttia
   (`.juttutyyppien-laatu` 130px, `.aikajana-luvut` 80px, `.nostot` 130px) ovat
   painoarvoltaan (0,2,0) vahvempia kuin tama saanto (0,1,0) ja asettavat
   `width`in. Flex-itemilla `flex-basis` maaraa paamitan `width`in yli, joten
   varianttien kapeat sarakkeet eivat vuoda pinottuun tilaan — ja kun 480px
   palauttaa `flex: 0 0 auto`, ne alkavat taas patea ilman etta niihin
   kosketaan. `width: 100%` havisi jokaiselle kolmelle.
   Katkaisukielto on pakotettu: test-css-style.sh [7/7] + css-velka-merkit.json
   § porautumis_selite_katkaisu (nollatoleranssi). */
.porautumis-selite {
  flex: 0 0 100%;
  font-size: 0.85rem;
  text-align: left;
  overflow-wrap: break-word;
  color: var(--color-text);
}
/* SELITTEEN ALARIVI (#1321, palaute #349): *"pystyiskö henkilön nimen perässä
   olevan puolueen ja tittelin ottamaan linkistä ulos, ja pienemmällä nimen
   alapuolelle. Ois helpompilukuinen tää lohko."*

   `display: block` on koko saanto — kaikki muu tulee valmiina: koko ja vari
   `small.info-teksti`-luokalta (.75rem / --color-muted, app.css:1196) eika
   uutena font-size-arvona (`css-velka-merkit.json § font_size_max` 229/232), ja
   rivittyminen `.porautumis-selite`n `overflow-wrap: break-word`ista jonka
   alarivi perii. Selite on flex-item eli blockified, joten `block`-lapsi saa
   oman rivinsa ilman `<br>`:aa.

   ⚠️ EI `white-space`, `overflow` EIKA `text-overflow` — `.porautumis-selite`n
   katkaisukielto on NOLLATOLERANSSI ja sen kate on koko selektori JALKELAISINEEN
   (`css-velka-merkit.json § porautumis_selite_katkaisu`, `test-css-style.sh`
   [7/7]). Puolue ja titteli joko luetaan tai niita ei ole — sama juurisyy kuin
   #918/#971/palaute #204.

   ⚠️ EI omaa `margin`ia: 480px+ -lohkossa selite on `text-align: right`, ja
   alarivi perii sen automaattisesti — oma marginaali rikkoisi oikean reunan
   linjan jota koko sarake noudattaa. */
.porautumis-selite-meta {
  display: block;
}
.porautumis-rata {
  flex: 1;
  height: 1.2rem;
  background: var(--color-surface-mid);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.porautumis-taytto {
  height: 100%;
  border-radius: var(--radius-md);
  transition: width .3s ease;
  min-width: 2px;
}
.porautumis-arvo {
  font-size: 0.8rem;
  min-width: 50px;
  text-align: right;
  color: var(--color-muted);
  flex-shrink: 0;
}
/* Rinnakkainen asettelu palautetaan siita leveydesta jossa se alkaa toimia
   (perustelu perussaannon kommentissa ylla). Lohko on TASSA eika tiedoston
   alaosassa, koska se on samaa painoarvoa (0,1,0) kuin muokkaamansa
   perussaannot — jarjestys ratkaisee. Alla olevat variantit ovat (0,2,0)
   eivatka valita sijainnista.

   ARVOSOLU KIINTEALLE LEVEYDELLE (#1201): RATA SAA MITATA VAIN ARVOA.
   Perussaannon `.porautumis-arvo { min-width: 50px; flex-shrink: 0 }` ILMAN
   `width`ia antaa solulle sen SISALLON mitan, jolloin `.porautumis-rata
   { flex: 1 }` saa vain jaannoksen — palkin pituus mittaa arvotekstin
   merkkimaaraa eika arvoa, ja samanarvoiset rivit piirtyvat eri pituisina.
   Sama valheluokka jonka `maxValue` estaa, eri akselilla: `maxValue` vahtii
   tayton osuutta radasta, tama radan omaa pituutta. Mitattu tuotannon
   DOM:ista 2.9.2026 (/haku?q=nato, 1600px): perussaannon 14 radan pituus
   1048–1108 px eli hajonta 60 px, kun `.aikajana-luvut`-lohkon hajonta
   samalla sivulla oli 0 px (#1063:n korjaus ehja).

   MIKSI PERUSSAANTOON EIKA NELJANTEEN SCOPE-LUOKKAAN: #1034 ja #1063
   paikkasivat saman vian omille lohkoilleen; juurisyy on tassa saannossa,
   ja neljas paikkaus olisi sama vika neljatta kertaa.

   MIKSI 120px: pisin perussaannon arvo on `323 · vinouma 41.1` (~110 px,
   mitattu tuotannon HTML:sta 3.9.2026) + marginaali. Yhtasuuruus EI riipu
   siita osuiko vakio oikeaan — solussa ei ole `white-space: nowrap`, joten
   vakiota pidempi teksti rivittyy solun SISALLA eika leveada sita (sama
   perustelu kuin #1063:n 210 px:lla).

   MIKSI VAIN >= 560px (#1250; oli 480px kunnes 5.9.2026): alle sen arvosolu
   saa SISALLON mitan, koska 120 px:n vakio soisi siella radan (360 px:lla
   238px -> 120px). Mitattu perustelu ja taulukko ovat oman 560px-lohkon
   kommentissa alla.

   ⚠️ GEOMETRIA VAIHTUU BREAKPOINTILLA: alle 560 px arvosolu on sisaltomittainen
   (`rata = V − 242 − arvo`), siita ylospain kiintea (`rata = V − 362`).
   ⚠️ ALA KAYTA #1180:n lukua `viewport − 290` — se oli laskettu arvosolun
   `min-width`illa 50 px, mutta solu ottaa SISALTONSA mitan ja todellinen
   sisalto on 89,7–109,1 px (mitattu srcdoc-iframella 5.9.2026, `/haku?q=nato`
   kortti *Mediat*). Perussaannon oikea vakio on siis `V − 332…352`, ja juuri
   tama liian optimistinen luku sai #1180:n paattelemaan etta ~180 px:n rata
   tayttyy 470 px:lla — todellisuudessa vasta ~511–531 px:lla.
   Alle 560 px:lla radat ovat keskenaan ERI PITUISIA mutta PIDEMPIA;
   >= 560 px:lla YHTA PITKIA. #1250
   korjasi tassa #1201:n regression: kiintea arvosolu 480px:sta lahtien
   painoi radan 480–560 px:lla alle luettavan ~180 px:n (480 px -> 118 px)
   eli palautti palautteen #286 juuri sille valille jolla #1180 sen korjasi.

   ⚠️ HINTA joka on tietoinen: pelkkia kokonaislukuja kantava lohko
   (`poliittinen-per-media`, arvot kuten `323`) saa 120 px:n arvosarakkeen eli
   ~70 px tyhjaa. Vastineeksi arvosarake on samassa linjassa kortista toiseen
   ja radat vertailukelpoisia. Jos tyhja osoittautuu liialliseksi, ratkaisu on
   lohkokohtainen KAPEAMPI scope-saanto — ei paluu sisaltomittaan.

   ⚠️ PAINOARVO (sama ansa kuin #1034/#1063): `.juttutyyppien-laatu` ja
   `.nostot` asettavat `.porautumis-arvo { min-width: 92px }` (0,2,0) joka
   voittaa taman `min-width`in, mutta `width` tulee tasta koska niilla ei ole
   omaa — ne saavat siis 120 px:n arvosolun >= 560px:lla (alle sen 92 px +
   sisaltomitta). Se on haluttua:
   sama vika oli niissakin. `.aikajana-luvut .porautumis-arvo` asettaa OMAN
   `width`insa (0,2,0) -> se on taysin koskematon. */
@media (min-width: 480px) {
  .porautumis-palkit { gap: var(--space-1); }
  .porautumis-palkkirivi { flex-wrap: nowrap; gap: var(--space-2); }
  .porautumis-selite { flex: 0 0 auto; width: 160px; min-width: 160px; text-align: right; }
}
/* ARVOSOLUN VAKIO OMALLA, KORKEAMMALLA BREAKPOINTILLA (#1250, Teemun paatos
   5.9.2026). #1201 laittoi taman 480px-lohkoon, jolloin rinnakkainen asettelu
   ja kiintea arvosolu alkoivat samasta leveydesta — ja se painoi radan
   480–560 px:lla alle luettavan mitan eli palautti palautteen #286 juuri
   sille valille jolla #1180 sen korjasi. Kaksi eri asiaa tarvitsee siis kaksi
   eri breakpointtia: ASETTELU palautuu 480px:lla, ARVOSOLUN VAKIO 560px:lla.

   MITATTU GEOMETRIA (chrome 66px = .container 2x--space-3 + .porautumis-kortti
   2x--space-3 + border 2x1; rivissa 3 itemia = 2 gappia = 2x--space-2 = 16px):
     rata = V − 66 − selite 160 − 16 − arvo
   MITATTU srcdoc-iframella 5.9.2026 (`/haku?q=nato` kortti *Mediat*, 8 rivia;
   ei laskettu — sisaltomitan sarake on TODELLINEN vaihteluvali, ei min-widthin
   antama teoreettinen ylaraja):
     V(px)   arvo 120px   arvo = sisaltomitta (89,7–109,1 px)
     480       118          128,9–148,3
     520       158          168,9–188,3
     540       178          188,9–208,3
     559       197          207,9–227,3
     560       198  <-      (vaihtuu 120px:aan)
     600       238            —
   Luettava ~180 px:n rata tayttyy kiintealla arvosolulla vasta 542 px:lla;
   560 on se + marginaali ja Teemun valitsema piste.

   ⚠️ 480–500 px JAA YHA ALLE 180 px:n perussaannossa, eika ole koskaan ollut
   sen ylapuolella — #1180:n 470 px oli laskettu 50 px:n arvosolulla (ks. ylla).
   #1250 parantaa jokaista leveytta (480 px: 118 -> 128,9–148,3) mutta ei nosta
   perussaantoa 180 px:aan 480 px:lla. Siksi palaute #286 EI ole kuitattu tassa,
   vaan jaa auki Teemun laitetarkistusta varten.

   VARIANTEISSA KORJAUS ON PUHDAS VOITTO (mitattu samalla ajolla, etusivu:
   `.nostot` x4 + `.juttutyyppien-laatu`, selite 130 px): niiden arvosisalto on
   kapeampi kuin `min-width: 92px`, joten lattia pitaa solun silti JOKA RIVILLA
   YHTA LEVEANA — rata 480 px:lla 148 -> 176 px ja hajonta 0 px. Sisaltomittaan
   palaaminen maksaa vertailukelpoisuutta vain perussaannossa.

   MIKSI 560 EIKA OLEMASSA OLEVA 600/641: molemmat kelpaisivat laskennallisesti,
   mutta jokainen niiden ja 560:n valinen pikseli on leveytta jolla radat ovat
   taas eri pituisia — eli #1201:n korjaus ei olisi siella viela voimassa. 560
   on aikaisin piste jossa MOLEMMAT ehdot tayttyvat yhta aikaa: rata >= 180 px
   JA radat keskenaan yhta pitkia. Mitattu piste, ei kopioitu breakpointti —
   sama perustelutapa kuin #1180:n 480 px:lla. (`spec/frontend-css.md`
   § Responsive-breakpointit: "Lisaa harvoin, perustele".)

   480–560 px ON TIETOINEN HINTA: siella arvosolu saa sisaltonsa mitan
   (perussaanto `min-width: 50px`, `.juttutyyppien-laatu`/`.nostot` 92px), joten
   radat ovat keskenaan eri pituisia — mutta PIDEMPIA. Lukukelpoisuus (#286)
   voittaa vertailukelpoisuuden silloin kun rata muuten kutistuisi 118 px:aan;
   yli 560 px:lla molemmat toteutuvat.

   ⚠️ PAINOARVOT PYSYVAT ENNALLAAN (toistuva ansa tassa tiedostossa):
   `.juttutyyppien-laatu .porautumis-arvo` ja `.nostot .porautumis-arvo`
   (`min-width: 92px`, 0,2,0) voittavat perussaannon `min-width`in, mutta
   `width` tulee TASTA lohkosta koska niilla ei ole omaa -> niiden arvosolu on
   120 px vasta >= 560 px, alle sen 92 px + sisaltomitta.
   `.aikajana-luvut .porautumis-arvo` asettaa OMAN `width`insa (0,2,0) -> se on
   taysin koskematon, eika sen 769px:n breakpointtiin kosketa. */
@media (min-width: 560px) {
  .porautumis-arvo { width: 120px; min-width: 120px; }
}
/* Etusivun juttutyyppilohko (#1034): selite tarvitsee leveyden, arvo tilan.
   Perustelu paivitetty #1180:ssa: perussaanto ei enaa katkaise mitaan (selite
   rivittyy), joten taman saannon tehtava on pitaa "Mielipidekirjoitus" (~122px)
   YHDELLA RIVILLA silloin kun asettelu on rinnakkainen — TAMAN lohkon ainoa
   tehtava on tyyppien vertailu, ja kaksiriviset selitteet rikkoisivat rivien
   tasakorkeuden. Arvokentta kantaa kaksi lukua ("69,4 · n 12 001") jotka eivat
   mahdu perussaannon 50px:aan.
   ⚠️ Nama ovat tarkennuksia olemassa olevaan komponenttiin, eivat sen kopioita:
   komponentin omiin saantoihin ei kosketa (11 kayttopaikkaa). Sivuvaikutus:
   selektorin painoarvo (0,2,0) ylittaa 480px-media queryn saannon (0,1,0),
   joten myos desktopilla selite on 130px eika 160px. Alle 480px lohko on
   pinottu eika tama leveys pade lainkaan (`flex-basis` voittaa `width`in) —
   pidin sen yhtena saantona ettei lohko kasvata CSS-velkaa. */
.juttutyyppien-laatu .porautumis-selite { width: 130px; min-width: 130px; }
.juttutyyppien-laatu .porautumis-arvo { min-width: 92px; }

/* Hakusivun aikajanan bucket-luvut (#1063): RATA SAA MITATA VAIN LUKUA.
   Perussaannon `.porautumis-arvo { min-width: 50px; flex-shrink: 0 }` antaa
   solulle sen sisallon leveyden, joten 35-40 merkin varausteksti
   ("48,1 · ±22,5 · n 120 · 7 mediaa · v12–18") tyonsi sen 160-207 px:aan ja
   `.porautumis-rata { flex: 1 }` sai vain jaannoksen. Mitattu tuotannon DOM:ista
   28.8.2026: radat 951-998 px (47 px hajonta) 1600 px:lla ja 0,8-48 px 390 px:lla.
   Kaksi SAMANARVOISTA jaksoa piirtyi siis eri pituisina sen mukaan kuinka pitka
   niiden varausteksti sattui olemaan — sama valhe jonka `maxValue = 100` estaa
   pystysuunnassa, vain eri akselilla. Mobiilissa palkkia ei ollut lainkaan.

   KIINTEA LEVEYS MOLEMMILLE REUNASOLUILLE tekee radan leveydesta rivista
   riippumattoman: kun selite ja arvo ovat joka rivilla yhta leveat, jaannos on
   sekin joka rivilla sama. Yhtasuuruus EI riipu siita osuiko vakio oikeaan —
   `.porautumis-arvo`lla ei ole `white-space: nowrap`, joten vakiota pidempi
   teksti rivittyy solun sisalla eika leveada. Vakio 210px on mitattu pisin
   (207px) + marginaali; selite 80px on ~2x pisin todellinen label (7 merkkia:
   "Q2/2024", "05/2026") eika siksi voi katketa ellipsiin.

   MOBILE-FIRST (spec/frontend-css.md): perussaanto = kapea ruutu, jossa arvo on
   OMALLA RIVILLAAN ja rata saa koko leveyden. 390 px:lla 210 px:n arvosolu jattaisi
   radalle ~50 px. Teemun § 8.2 -paatos sailyy: luku ja varaus ovat yha SAMASSA
   solussa vierekkain — vain solun paikka suhteessa palkkiin muuttuu.

   ⚠️ PAINOARVOANSA (sama kuin #1034 ylla): kahden luokan selektori (0,2,0)
   ylittaa `@media (min-width: 480px)` -lohkon `.porautumis-selite`-saannon
   (0,1,0), koska media query ei lisaa painoarvoa. Selite on siis 80px MYOS
   desktopilla eika 160px. Se on tassa haluttua — bucket-labelit ovat 4-7
   merkkia, ja 80px vapauttaa 80px lisaa radalle. (#1180: perussaanto pinoaa
   selitteen alle 480px:n, jolloin tama leveys ei pade — arvo on jo ennestaan
   omalla rivillaan, joten kapealla naytolla rivi on selite / rata / arvo.)
   ⚠️ Tarkennuksia olemassa olevaan komponenttiin, EIVAT sen kopio: komponentin
   omiin saantoihin ei kosketa (13 kayttopaikkaa). */
.aikajana-luvut .porautumis-selite { width: 80px; min-width: 80px; }
.aikajana-luvut .porautumis-palkkirivi { flex-wrap: wrap; }
.aikajana-luvut .porautumis-arvo { width: 100%; min-width: 0; text-align: left; }
@media (min-width: 769px) {
  .aikajana-luvut .porautumis-palkkirivi { flex-wrap: nowrap; }
  .aikajana-luvut .porautumis-arvo { width: 210px; min-width: 210px; text-align: right; }
}

.porautumis-rata.pinottu {
  display: flex;
  background: none;
}
/* Palkin PAIKALLA nakyva otosvaraus (#971): kun n <= 2, palkkia ei piirreta
   lainkaan (yhden jutun pinottu palkki on aina 100 % yhta varia). Teksti ottaa
   palkin paikan samalla flex-suhteella, jotta oikean reunan lukusarake pysyy
   linjassa muiden rivien kanssa. Vari tulee .otos-varaus-luokalta. */
.porautumis-varaus {
  flex: 1;
}
.porautumis-segmentti {
  height: 100%;
  transition: width .3s ease;
  min-width: 1px;
}
/* Erotinviiva pinottujen segmenttien valiin (saavutettavuus 8.9.2026).
   MIKSI: neljalla pinotulla luokalla valkoisella pohjalla on matemaattisesti
   mahdotonta saada joka vierekkainen pari 3:1:een (4 x 3:1 = 27:1 > maksimi
   21:1). Kun segmentit eivat kosketa toisiaan, vierekkaisyysvaatimus katoaa
   kokonaan — ja se on halvempi kuin paletin kiristaminen. Mitattu tarve:
   kuultu-asteikon vaalein pari on 2,23:1 ja vastakkainasettelun 2,66:1.

   MIKSI `box-shadow: inset` EIKA `border-left`: segmentin leveys tulee
   INLINE-PROSENTTINA (Type-C custom property, `--osuus`). `border` kuuluu
   laatikkomalliin ja levittaisi segmenttia tai — `box-sizing: border-box`in
   kanssa — syo prosenttileveydesta, eli palkin osat eivat enaa summautuisi
   sataan. `inset`-varjo piirtyy laatikon SISALLE ilman tilaa, joten prosentit
   pysyvat totuudenmukaisina. Ei myoskaan riko `.porautumis-rata`n
   `overflow: hidden` + `border-radius` -kuorta.

   `min-width` 1px -> 3px VAIN erottimen saavalla segmentilla: 1px:n segmentti
   jonka paalla on 1px valkoinen viiva olisi kokonaan valkoinen eli KATOAISI.
   Ensimmainen segmentti ei saa erotinta, joten sen 1px jaa ennalleen. */
.porautumis-segmentti + .porautumis-segmentti {
  box-shadow: inset 1px 0 0 var(--color-bg);
  min-width: 3px;
}
/* --- segmentin pintakuvio (#1321, saavutettavuus) ---
   MIKSI: `artefaktit/varipalkit-saavutettavuus.md` — *"koska vari ei ikina ole
   ainoa kanava: anna 'Ei sovellu' -osalle vinoviivakuvio (pattern) varin
   lisaksi"* (WCAG 1.4.1). `not_applicable` ei ole vaaleusasteikon neljas askel
   vaan sen ULKOPUOLELLA (se on nimittajan nakyva osa), ja jarjestysasteikko ei
   voi kertoa sita: harmaa on ilman kuviota vain neljas savy muiden joukossa.

   🔴 MIKSI PSEUDO EIKA `background-image`: pinotun segmentin tausta tulee
   INLINE-TYYLINA (`style="width:X%;background:<vari>"`). Inline voittaa
   ulkoisen CSS:n, ja `background` on SHORTHAND joka nollaa `background-image`n
   arvoon `none` — ulkoinen image-saanto ei siis nakyisi lainkaan.
   `!important` toimisi mutta on huonompi: se voittaisi myos segmentin varin
   jos kuvio joskus kirjoitetaan shorthandilla. Rinnakkaisessa osassa inline on
   vain `--osuus`/`--vari` (Type-C) eli siella image toimisi, mutta kaksi eri
   mekanismia SAMALLE kuviolle ajautuisi erilleen — yksi saanto, kolme
   kayttopaikkaa (segmentti, rinnakkaisosa, legendapallo).

   ⚠️ `inset: 0 0 0 1px` EIKA `inset: 0`: pinottujen segmenttien erotinviiva on
   `box-shadow: inset` (ks. ylla) ja pseudo piirtyy varjon PAALLE, joten
   tayspeittava pseudo hukuttaisi erottimen juuri silla segmentilla jolla kuvio
   on. Yhden pikselin vasen kaista jattaa sen nakyviin. Legendapallolla ja
   rinnakkaisosalla erotinta ei ole, mutta sama arvo riittaa niille (1px 0.65rem
   pallosta ei ole havaittavissa) — yksi saanto on luettavampi kuin kolme.

   KUVION VARI on `--color-asteikko-kuvio` (valkoinen, 55 %): se on SAMA vari
   kuin erottimessa, eli kuvio nayttaa raidoiksi leikatulta segmentilta eika
   uudelta varilta. Peitto on osittainen jotta pohjavari pysyy tunnistettavana
   raitojen valissa — TAYSI valkoinen puolittaisi segmentin efektiivisen
   kontrastin taustaa vasten. `--color-asteikko-neutraali` (#4B5563) on 7,56:1
   valkoista vasten, joten 55 %:n raidoituksella heikoin kohta on yha selvasti
   yli 3:1:n pintavaatimuksen.

   `pointer-events: none`: segmentin `title`-tooltip lukee segmentilta itselta,
   eika pseudo saa siepata hiirta sen edesta. */
.porautumis-segmentti.kuvio-viivat,
.porautumis-osa.kuvio-viivat,
.porautumis-piste.kuvio-viivat {
  position: relative;
}
.porautumis-segmentti.kuvio-viivat::after,
.porautumis-osa.kuvio-viivat::after,
.porautumis-piste.kuvio-viivat::after {
  content: "";
  position: absolute;
  inset: 0 0 0 1px;
  pointer-events: none;
  background-image: repeating-linear-gradient(
    135deg,
    var(--color-asteikko-kuvio) 0,
    var(--color-asteikko-kuvio) 2px,
    transparent 2px,
    transparent 5px
  );
}
/* --- rinnakkainen rata (#1298, palaute #324) ---
   Pinotussa palkissa vain ensimmainen ja viimeinen segmentti alkavat samasta
   kohdasta, joten KESKIMMAISEN segmentin pituutta ei voi verrata rivien
   valilla. Rinnakkaiset urat antavat jokaiselle segmentille oman
   perusviivansa. Lohko on TASSA `.pinottu`:n vieressa eika tiedoston
   alaosassa: sama painoarvo (0,2,0), jarjestys ratkaisee.

   URAN KORKEUS 0.25rem -> 0.4rem JA RYTMI-INVARIANTTI PURETTU TIETOISESTI
   (palaute 8.9.2026, Teemun oma mittaus tuotannosta: *"palkit on tosi ohuita,
   vois olla leveampia"*). Lohko piti aiemmin kokonaiskorkeuden lahella
   perussaannon 1.2rem:aa (4 x 0.25rem + 3 x 1px ~= 1.19rem) jotta rytmi ei
   hyppaa kun sama komponentti esiintyy molemmissa muodoissa samalla sivulla.
   4 x 0.4rem + 3 x 1px ~= 1.79rem eli rytmi HYPPAA — ja se hyvaksytaan, koska
   ohut ura on luettavuusongelma ja rytmiero on esteettinen. Vaihtokauppa on
   tehty tahan suuntaan Teemun mittauksen perusteella; jos rinnakkaislohkon
   korkeus alkaa haitata, oikea korjaus on rivivalin sailytys EIKA uran
   ohentaminen takaisin. Perussaannon `height` on yha kumottava (`auto`),
   muuten urat litistyisivat 1.2rem:n sisaan.

   RIVIVALI: rinnakkaismuoto tarvitsee enemman ilmaa rivien valiin kuin
   pinottu, koska rivi on nyt 4 uraa korkea eika yksi rata. Vali on ALLA
   omassa saannossaan (`.porautumis-palkit:has(.rinnakkain)`) eika tassa,
   koska se koskee RIVIEN valia (`.porautumis-palkit`in `gap`) eika radan
   sisaista rakennetta. */
.porautumis-rata.rinnakkain {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
  height: auto;
  background: none;
  overflow: visible;
}
.porautumis-ura {
  height: 0.4rem;
  background: var(--color-surface-mid);
  border-radius: var(--radius-md);
  overflow: hidden;
}
/* RIVIVALI VAIN RINNAKKAISMUODOSSA (palaute 8.9.2026: *"naihin vierekkaisiin
   palkkihommiin tarvitaan myos valia ennen seuraavaa"*). Teemu ehdotti
   `.porautumis-palkkirivi { padding-bottom: 0.75rem }` ja sanoi ettei tiedä
   mika on paras maaritystapa. Valittu `gap` rivien SAILIOSSA eika
   `padding-bottom` riveilla, kahdesta syysta: (1) `gap` ei tuota tyhjaa tilaa
   VIIMEISEN rivin alle eika siten kortin sisapohjaan, jonka `padding` hoitaa
   jo; (2) perussaanto ja 480px-lohko saataavat samaa `gap`ia, joten vali on
   yhdessa paikassa eika kahdessa mekanismissa.
   `:has()` ON kaytossa jo (esim. `.porautumis-kortti:has(...)`-kuviot) ja
   tuettu kaikissa kohdeselaimissa; jos se joskus pitaa purkaa, korvaava tapa
   on muotoluokka komponentista (`poliittinen-per-media` tietaa muodon).
   MIKSI EI perussaantoon: pinotut ja yksipalkkiset lohkot ovat 1.2rem korkeita
   ja niiden rytmia ei pyydetty muuttamaan — koko sivuston valistys yhden
   muodon takia olisi eri muutos kuin taman palautteen pyytama. */
.porautumis-palkit:has(.rinnakkain) { gap: 0.75rem; }
/* `--osuus` ja `--vari` tulevat INLINE rivilta (Type-C, ks. komponentin
   kommentti) eivatka voi olla `:root`issa — siksi fallback on pakollinen, ja se
   on myos oikea kayttaytyminen: ilman arvoa ura on tyhja eika `auto`-levyinen. */
.porautumis-osa {
  width: var(--osuus, 0%);
  height: 100%;
  background: var(--vari, var(--color-primary));
  transition: width .3s ease;
}
/* --- legenda lajitteluohjaimena (#1298) ---
   PICON NAPPITYYLIEN KUMOUS. Osuva haara `<button type="button">`:lle on
   `[role=button],[type=button],…,button{…}` jonka KORKEIN OSUVA haara on
   `[type=button]` = (0,1,0) — ei `button` (0,0,1), joka on saman lohkon
   matalin haara (spec/frontend-css.md § Mitoita korkeinta osuvaa haaraa
   vastaan). Hover-/focus-haara `[type=button]:is(:hover,:active,:focus)` on
   (0,2,0) mutta asettaa VAIN `--pico-*`-muuttujia, joita nama deklaraatiot
   eivat lue. `.porautumis-selitteet button.rivi` = (0,2,1) voittaa molemmat.
   `font: inherit` + `color: inherit` hakevat legendan oman typografian
   (0.8rem / --color-muted) — nappi ei saa nayttaa napilta, se on
   lajiteltavan sarakkeen nimi.

   ⚠️ PYSTY-PADDING ON KOSKETUSKOHDE, EI TYYLI (WCAG 2.2 AA 2.5.8, Target Size
   Minimum = 24x24 px). Ilman sita nappi on 360 px:n leveydella 19 px korkea:
   0.8rem = 12.8 px fontti + rivivali. `--space-1` (4 px) ylos ja alas nostaa
   sen 27 px:iin. Legenda oli ennen #1298:aa ei-interaktiivinen span, jolloin
   19 px oli kunnossa — nappihaara ei ole. Vaakapadding pysyy 0:ssa: leveyden
   tuo sarakkeen nimi, ja nolla pitaa legendan vasemman reunan linjassa
   span-haaran (`/yrityksille`) kanssa. Sailyta >= 24 px myos jos fonttikoko
   pienenee. */
.porautumis-selitteet button.rivi {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
  padding: var(--space-1) 0;
  border: none;
  border-radius: 0;
  background: none;
  box-shadow: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.porautumis-selitteet button.rivi:focus-visible {
  outline: 2px solid var(--color-outline);
  outline-offset: 2px;
}
.porautumis-selitteet button.rivi[aria-pressed="true"] {
  color: var(--color-text);
  font-weight: 600;
}
.porautumis-jarjestys:not(:empty) {
  margin-left: var(--space-1);
}
.porautumis-selitteet {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
  font-size: 0.8rem;
  color: var(--color-muted);
}
.porautumis-selitteet .rivi {
  display: flex;
  align-items: center;
  gap: 0.3rem;
}
.porautumis-piste {
  display: inline-block;
  width: 0.65rem;
  height: 0.65rem;
  border-radius: 2px;
  flex-shrink: 0;
}
/* Värivariantit (#383, 6.5.2026) — korvaa topic.php inline-tyylit:
   style="background:var(--color-poliittinen-vasen)" → .porautumis-piste.vasen */
.porautumis-piste.vasen    { background: var(--color-poliittinen-vasen); }
.porautumis-piste.keskusta { background: var(--color-poliittinen-keskusta); }
.porautumis-piste.oikea    { background: var(--color-poliittinen-oikea); }
.porautumis-lista {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.porautumis-lista .rivi {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
}
.porautumis-lista .rivi:last-child {
  border-bottom: none;
}

/* --- nosto (#1152) ---
   Etusivun nostolaatikko: vaite (h2) + 1-3 lausetta + VISU jonka virkaa hoitaa
   oikea dynaaminen data + varaus. Komponentti `domain/Views/components/nosto.php`,
   lista `nostot.php`; kaytto etusivun `nostot`-lohkossa ja /paikat-landarilla
   (#1451). `.nostot` on siksi scopettamaton — ei `body.sivu-etusivu`-rajausta.

   ⚠️ Visu tulee valmiiksi renderoituna toiselta komponentilta (artikkelikortti,
   porautumis-palkit), joten TAALLA EI SAA TYYLITTAA VISUN SISALTOA. `.nosto .visu`
   on pelkka kaare joka antaa valin — jos visun ulkoasua pitaa saataa nostossa,
   se on merkki siita etta visukomponentti tarvitsee variantin, ei siita etta
   nosto tarvitsee jalkelaisselektorin.

   No-Line Rule (spec/frontend-css.md § 9): erottelu taustavarilla, ei
   1px-bordereilla. Nosto on `--color-surface` "paperi" `--color-bg` -pohjalla —
   sama pinta-hierarkia kuin muillakin laatikoilla, jotta peräkkäiset nostot
   erottuvat toisistaan ilman viivoja.

   Mobile-first: perussaanto on kapea ruutu (nostot allekkain, tiiviimpi
   padding), 768px lisaa väljyyden. */
.nostot {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.nosto {
  background: var(--color-surface);
  border-radius: var(--radius-md);
  padding: var(--space-3);
}
.nosto h2 {
  font-size: 1.25rem;
  line-height: 1.25;
  margin: 0 0 var(--space-2);
  color: var(--color-text);
}
.nosto .visu {
  margin-top: var(--space-3);
}
/* Varaus kuuluu VISUN yhteyteen eika leipatekstiin (markkinointi/huomiot/H001:
   "jos varaus ei mahdu nostoon, nosto ei mahdu etusivulle"). Tiivis ylamarginaali
   kiinnittaa sen visuun; ilman sita se lukisi kuin uusi kappale. */
.nosto .varaus {
  margin-top: var(--space-2);
  margin-bottom: 0;
}
.nosto .linkki {
  margin-top: var(--space-2);
  margin-bottom: 0;
}
/* Populisminoston palkkien selite ja arvo (#1152): SAMA tarkennus samasta
   syysta kuin `.juttutyyppien-laatu`illa (#1034). Perussaanto katkaisi
   selitteen "Mielipidekirjoitus" (#1180 poisti katkaisun perussaannosta —
   ks. sen kommentti ylla), ja arvokentta kantaa kaksi lukua
   ("28,1 · n 998") jotka eivat mahdu perussaannon 50px:aan. Tarkennus tehtiin
   ennakoivasti eika selaintestin jalkeen, koska kyseessa on TASMALLEEN sama
   selite ja sama lukumuoto jonka #1034 jo mittasi katkeavaksi.
   ⚠️ Tarkennuksia olemassa olevaan komponenttiin, EIVAT sen kopio: komponentin
   omiin saantoihin ei kosketa (14 kayttopaikkaa). Sama painoarvoansa kuin
   #1034/#1063: kahden luokan selektori (0,2,0) ylittaa 480px-media queryn
   saannon (0,1,0), joten leveydet patevat myos desktopilla. */
/* #1153: selite saa rivittya nostoissa (`white-space`/`overflow` alla). Mitattu tuotannon DOMista 1.9. klo 23:5x
   (13 selitetta): kaikki muut mahtuvat 130px:aan tasan, mutta "Vastakkainen
   nakokulma" on 153px ja katkesi ellipsiin — juuri se rivi jonka otsikko
   nimeaa ("2,3 kertaa yleisempi kuin vastakkainen nakokulma").
   ⚠️ MIKSI RIVITYS EIKA LEVEAMPI SARAKE: molemmat mitattiin samassa DOMissa
   pakottamalla `.nostot`-kaareen 358px (= 390px laite) ja 288px (= 320px).
   152px:n sarake korjasi 390px:n mutta rikkoi 320px:n (13 vuotavaa elementtia,
   kaare yli 5px). Rivitys ei vuoda kummallakaan, ja se jattaa palkkiradalle
   390px:lla 54px kun leveampi sarake jatti 32px — eli kapeampi sarake tekee
   itse palkista luettavamman. Selitteen korkeus 20px -> 41px vain silla
   yhdella rivilla jolla teksti ei mahdu.
   Komponenttiin ei kosketa (14 kayttopaikkaa), tama on sama scoped-tarkennus
   kuin ylla. Saannot ovat SAMASSA lohkossa eivatka omanaan: `test-css-style.sh`
   kieltaa duplikaattiselektorin, ja se on oikeassa — kaksi `.nostot
   .porautumis-selite` -lohkoa ajautuisi erilleen.
   #1180: `white-space: normal` ja `overflow: visible` POISTETTU — ne olivat
   vain perussaannon `nowrap`/`hidden`in kumoamista, eika perussaanto enaa
   aseta niita. Leveys 130px jaa ja pate 480px:sta ylospain; sen alla pinotettu
   perustila korvaa taman kokonaan, eli "Vastakkainen nakokulma" (153px) saa
   koko kortin leveyden eika 130px:n saraketta. 1.9. mittaus jaa ylle
   historiana: se perusteli miksi rivitys valittiin leveamman sarakkeen sijaan,
   ja #1180 yleisti saman valinnan koko komponenttiin. */
.nostot .porautumis-selite {
  width: 130px;
  min-width: 130px;
}
.nostot .porautumis-arvo { min-width: 92px; }
@media (min-width: 768px) {
  .nosto { padding: var(--space-4); }
  .nosto h2 { font-size: 1.45rem; }
  /* Ilmionoston kaksi aaripaakorttia rinnakkain (#1285). Perussaanto
     (`.artikkelilista { flex-direction: column }`, osio artikkelilista) jaa
     voimaan mobiilissa: kaksi korttia 390px:n ruudulla rinnakkain on ahdas, ja
     allekkain ne luetaan silti pariksi koska ne ovat saman laatikon sisalla.
     `min-width: 0` on PAKOLLINEN eika kosmetiikkaa: ilman sita flex-item ei
     kutistu sisaltonsa alle, ja pitka otsikko tyontaa kortin laatikon yli.
     Sama korjaus kuin `.ranking-rivi > .artikkelikortti`illa.
     Kontekstivalitsin, ei uusi luokka (no-BEM, spec/frontend-css.md):
     komponenttiin ei kosketa, silla `.artikkelilista` on jaettu 14
     kayttopaikan kesken. */
  .nostot .visu .artikkelilista { flex-direction: row; }
  .nostot .visu .artikkelilista > * { flex: 1 1 0; min-width: 0; }
}
/* Kaksi nostoa rinnakkain (#1464, /yrityksille): aihe + kontrolli ja
   lehtivertailun kaksi palkkilistaa ovat PARI — vertailu on osion vaite, joten
   allekkain kontrolli lukeutuisi erilliseksi loydokseksi. Tilaluokka kaarelle
   `lisaluokka`-propsin kautta, ei uusi komponentti; alle rajan perussaanto
   (flex-column) jaa voimaan.
   ⚠️ RAJA ON 1024px EIKA 768px — laskettu, ei viela selaimessa mitattu:
   768px:ssa sarake = (736 - gap 24) / 2 = 356px, nosto-padding 2x24 ja kortin
   padding+border 34 jattavat 274px, ja nostojen palkkirivin selite 130px +
   arvo 92px + valit vievat siita 238px -> palkkiradalle ~36px. 1024px:ssa
   rata on ~160px. 1024 on olemassa oleva raja-arvo (yla-palkin navi), ei uusi.
   `minmax(0, 1fr)`: ilman nollaminimia SVG/palkkirivi venyttaisi sarakkeen
   yli puolikkaan (spec/frontend-css.md § Vaakaylivuoto). Kaaren asettelu, EI
   visun sisallon tyylitysta. */
@media (min-width: 1024px) {
  .nostot.kaksi {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Aikasarjan vihje (#831, palaute #152) — porautumis-aikasarja-komponentin
   hiiri-/kosketusvihje: mika ajankohta ja arvo piikin tai kuopan takana on.
   Sijainti lasketaan JS:ssa (aikasarja_tooltip_xdata, domain/helpers.php), CSS
   pitaa ulkoasun — Alpinen :style-sidonta osuisi inline-tyylimittariin.
   Fonttikoko peritaan <small>-elementilta (.75rem, osio 2): css-velka-merkit.json
   font_size_max on tasmalleen taynna eika sita saa nostaa. */
.aikasarja-kuvaaja {
  position: relative;
}
.aikasarja-kuvaaja svg:focus-visible {
  outline: 2px solid var(--color-outline);
  outline-offset: 2px;
}
.aikasarja-osuma {
  fill: transparent;
  pointer-events: all;
  cursor: pointer;
}
.aikasarja-korostus {
  fill: var(--color-bg);
  stroke: var(--color-text);
  pointer-events: none;
}
.aikasarja-vihje {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  pointer-events: none;
  white-space: nowrap;
  background: var(--color-surface-low);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-float);
  padding: var(--space-1) var(--space-2);
}
.aikasarja-vihje-otsikko {
  color: var(--color-muted);
}
.aikasarja-vihje-arvo {
  color: var(--color-text);
  font-weight: 600;
}
/* Paivan tilannekatsaus -linkki (#1437, palaute #330). Vihje on
   pointer-events: none jottei se varasta osumavyohykkeiden hoveria; linkki
   ottaa osoittimen takaisin. Klikattavissa kun valinta on lukittu. */
.aikasarja-vihje-linkki {
  pointer-events: auto;
}

/* Desktop: 2-sarakkeinen ruudukko. (Selitteen 160px siirtyi #1180:ssa
   480px-lohkoon komponentin oman lohkon sisalle — se tulee nyt sielta, eika
   sita saa maaritella tassa uudestaan.) */
@media (min-width: 769px) {
  .porautumis-ruudukko { grid-template-columns: repeat(2, 1fr); }
}

/* --- poliittinen-suuntaus ---
   Sidebar-kortti: horisontaalinen jakaumapalkki + legenda.
   Värit suomen mediakonvention mukaan: vasen-punainen, keskusta-harmaa, oikea-sininen
   (--color-poliittinen-*). Sama paletti kuin drilldown-kaavioissa — sama data, sama väri. */

.poliittinen-suuntaus {
  display: block;
  width: 100%;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-left: var(--accent-stripe) solid var(--color-primary);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-4);
}
@media (min-width: 768px) {
  .poliittinen-suuntaus { padding: var(--space-4); }
}
.poliittinen-suuntaus .palkki {
  display: flex;
  width: 100%;
  height: 12px;
  border-radius: var(--radius-full);
  overflow: hidden;
  margin: var(--space-2) 0;
  background: var(--color-muted);
}
.poliittinen-suuntaus .segmentti {
  height: 100%;
  transition: width var(--transition-base);
}
.poliittinen-suuntaus .segmentti.vasen   { background: var(--color-poliittinen-vasen); }
.poliittinen-suuntaus .segmentti.keskusta { background: var(--color-poliittinen-keskusta); }
.poliittinen-suuntaus .segmentti.oikea   { background: var(--color-poliittinen-oikea); }
.poliittinen-suuntaus .segmentti + .segmentti {
  box-shadow: inset 2px 0 0 var(--color-bg);
}
.poliittinen-suuntaus .selitteet {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-2);
}
.poliittinen-suuntaus .selitteet .rivi {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 0.8rem;
}
.poliittinen-suuntaus .piste {
  width: 10px;
  height: 10px;
  border-radius: var(--radius-full);
  flex-shrink: 0;
}
.poliittinen-suuntaus .piste.vasen   { background: var(--color-poliittinen-vasen); }
.poliittinen-suuntaus .piste.keskusta { background: var(--color-poliittinen-keskusta); }
.poliittinen-suuntaus .piste.oikea   { background: var(--color-poliittinen-oikea); }
.poliittinen-suuntaus .nimi {
  flex: 1;
  color: var(--color-text);
}
.poliittinen-suuntaus .arvo {
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}
/* Osuus lukumaaran perassa (#1151, palaute #232). .nimi on flex:1, joten arvo ja
   osuus pysyvat rivin oikeassa reunassa. min-width + oikea tasaus estavat
   sarakkeen hyppimisen kun rivien luvut ovat eri levyisia (7 % vs. 100 %). */
.poliittinen-suuntaus .osuus {
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
  min-width: 3.2em;
  text-align: right;
}
.poliittinen-suuntaus .yhteensa {
  font-size: 0.75rem;
  margin-top: var(--space-1);
  margin-bottom: 0;
}

/* Poliittinen kehystys — kompakti palkki (nakyma='palkki', task #560).
   Itsenainen ohut palkki kuten .vinouma-palkki; samat --color-poliittinen-*
   tokenit kuin .poliittinen-suuntaus .segmentti (ei uusia vareja). */
.poliittinen-suuntaus-palkki {
  display: flex;
  width: 100%;
  height: 8px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--color-surface);
  margin: var(--space-2) 0;
}
.poliittinen-suuntaus-palkki .segmentti {
  height: 100%;
  transition: width 0.3s ease;
}
.poliittinen-suuntaus-palkki .segmentti.vasen    { background: var(--color-poliittinen-vasen); }
.poliittinen-suuntaus-palkki .segmentti.keskusta { background: var(--color-poliittinen-keskusta); }
.poliittinen-suuntaus-palkki .segmentti.oikea    { background: var(--color-poliittinen-oikea); }

/* --- entiteetti-tilasto ---
   Tilastolukukortti entity-sivuilla (author, media, topic). */

.entiteetti-tilasto { text-align: center; }
.entiteetti-tilasto .arvo { font-size: 1.5rem; font-weight: 700; }
.entiteetti-tilasto .nimi { font-size: .75rem; color: var(--color-muted); }
/* Otoskoko (#952): labelin jatke SAMALLA rivilla, ei omaa rivia — kortissa on
   jo vertailurivi ja kolmas pieni rivi ison luvun alla olisi tehnyt kortista
   listan. Erotin " · " tulee markupista (sama syy kuin .vertailulla alla).
   Ei omaa font-sizea: .nimi on jo .75rem ja app.css:n font-size-kynnys on
   232 (test/css-velka-merkit.json) — opacity riittaa erottamaan otoksen.
   Himmennys ja kursiivi ovat LISA, eivat signaali: varaus ("suuntaa antava")
   on itse tekstissa, koska ruudunlukija ei nae kursiivia. */
.entiteetti-tilasto .nimi .otos { opacity: .8; }
.entiteetti-tilasto .nimi .otos.himmea { font-style: italic; }
/* Vertailuarvo (#884, palaute #203): "vrt. 33,7  9 mediaa" labelin alla.
   Mykistetympi kuin .nimi jotta paaluku sailyy kortin painopisteena.
   Ei omaa font-sizea .joukko-osalle (perii .vertailun) — app.css:n
   font-size-kynnys on 232 (test/css-velka-merkit.json) ja opacity riittaa. */
.entiteetti-tilasto .vertailu {
  margin-top: var(--space-1);
  font-size: .7rem;
  color: var(--color-muted);
}
/* Ei margin-leftia: vali tulee markupin " · "-erottimesta (#884-korjaus).
   Marginaali yksin jatti textContentin muotoon "vrt. 33,79 mediaa" — kaksi
   lukua liimautui yhteen ruudunlukijalle ja kopioidussa tekstissa. */
.entiteetti-tilasto .vertailu .joukko { opacity: .75; }

/* --- otos-varaus ---
   Otoskoon varaus taulukkorivilla (#952). Aihesivun mediakohtaisessa
   taulukossa vinoumaluvun perassa: "66,0 · yksi juttu". Erillinen luokka eika
   .entiteetti-tilaston .otos, koska konteksti on eri (taulukkosolu ilman
   .nimi-emoa) — jaettu perussaanto olisi vaatinut kumoamisen molemmissa.
   Erotin on markupissa, ei marginaalissa. */
.otos-varaus { color: var(--color-muted); opacity: .85; }
.otos-varaus.himmea { font-style: italic; }

/* --- rki (palaute #354, 8.9.2026) ---
   Puolue x media -ruudukon RKI-luvun suunta. Perustila on sama vaimennettu
   sävy kuin muilla solun metaluvuilla; variantti kertoo suunnan.

   VARIT SAVYASTEIKOLTA, EI VIHREA/PUNAINEN. Teemu pyysi palautteessa "esim
   vihreällä / punaisella" ja lisasi heti perään: "kato että näillä on joku
   kontrastiero — uudet värikoodit kun sellaiset on löydetty". Ne loytyivat
   (ks. § Asteikkovärit :root:ssa): arvostellaan = purppura, puolustetaan =
   oranssi. Samat tokenit kuin lohkon 5b palkeissa -> luku ja palkki puhuvat
   samaa kielta, eika sivulla ole kahta eri varikielta samasta mittarista.
   Puolustus-oranssi kaytetaan TEKSTIVARIANTTINA (#9A5A00, 5,47:1); palkin
   #E08214 on 2,85:1 eika kelpaa pienena tekstina.

   VARI EI OLE AINOA KANAVA (WCAG 1.4.1): markupissa on lisaksi nuolimerkki
   (aria-hidden) ja .sr-only-teksti suunnasta. Perusluokka `.rki` yksin =
   neutraali eli ei kumpikaan suunta. Variantti on (0,2,0) eli se voittaa
   .muted-utilityn (0,1,0) riippumatta lahdejarjestyksesta. */
.rki { color: var(--color-muted); }
.rki.kriittisempi { color: var(--color-savy-krit-teksti); }
.rki.suopeampi    { color: var(--color-savy-puol-teksti); }

/* --- sarake-lajittelu (#1427, palaute #355) ---
   Puolue x media -ruudukon sarakeotsikon lajittelunappi. Sama JS kuin
   legendalajittelussa (`porautumis-lajittelu.js`, taulukkotila) -> sama
   Picon kumous kuin `.porautumis-selitteet button.rivi`:lla ja samasta syysta:
   `[type=button]` = (0,1,0), tama (0,2,2) voittaa sen ja hover-haaran (0,2,0).
   KOSKETUSKOHDE 1.5rem = 24 px (WCAG 2.2 AA 2.5.8): napissa on vain merkki,
   joten koko tulee min-mitoista eika tekstista. Lepotilan merkki (↕) on
   vaimennettu; aktiivinen sarake (`aria-sort` asc/desc) saa tekstivarin. */
th button.sarake-lajittelu[data-lajittele] {
  -webkit-appearance: none;
  appearance: none;
  min-width: 1.5rem;
  min-height: 1.5rem;
  margin: 0 0 0 var(--space-1);
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  box-shadow: none;
  color: var(--color-muted);
  font: inherit;
  line-height: 1;
  cursor: pointer;
}
th button.sarake-lajittelu[data-lajittele]:focus-visible {
  outline: 2px solid var(--color-outline);
  outline-offset: 2px;
}
th[aria-sort="ascending"] button.sarake-lajittelu,
th[aria-sort="descending"] button.sarake-lajittelu {
  color: var(--color-text);
}
.sarake-lajittelu .porautumis-jarjestys:not(:empty) { margin-left: 0; }

/* --- tunnistautuminen ---
   Auth-sivujen layout ja kortti (login, verify, consent). */

.tunnistautuminen { background: var(--color-bg); min-height: 100vh; }
.tunnistautuminen main {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: var(--space-4);
}
.tunnistautuminen-kortti {
  max-width: 420px;
  width: 100%;
  text-align: center;
  padding: var(--space-5) var(--space-4);
}
.tunnistautuminen-kortti .logo {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  margin-bottom: 2rem;
}
.tunnistautuminen-kortti .logo img {
  width: 48px;
  height: 48px;
  border-radius: 8px;
}
.tunnistautuminen-kortti .logo strong {
  font-family: var(--font-body);
  font-size: 1.25rem;
  color: var(--color-text);
}
.tunnistautuminen-kortti .kuvaus {
  color: var(--color-muted);
  margin-bottom: var(--space-4);
}
.tunnistautuminen-kortti h2 {
  margin-bottom: var(--space-2);
}
.tunnistautuminen-kortti fieldset {
  text-align: left;
}
.tunnistautuminen-kortti fieldset label {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}
.tunnistautuminen-kortti fieldset label input[type="checkbox"] {
  flex-shrink: 0;
  margin-top: 0.2em;
}
.tunnistautuminen-kortti .ominaisuudet {
  list-style: none;
  padding: 0;
  margin: 0 0 2rem 0;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.tunnistautuminen-kortti .ominaisuudet li {
  list-style: none;
  font-size: .875rem;
  color: var(--color-muted);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.tunnistautuminen-kortti .ominaisuudet li::before {
  content: "\2713";
  color: var(--color-success);
  font-weight: 700;
  flex-shrink: 0;
}
.tunnistautuminen-kortti .painike {
  width: 100%;
}
.tunnistautuminen-kortti .alapalkki {
  margin-top: var(--space-5);
  font-size: .75rem;
  color: var(--color-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border-top: none;
  padding: 0;
}
.tunnistautuminen-kortti .alapalkki a {
  color: var(--color-muted);
  text-decoration: underline;
}
.tunnistautuminen-kortti .alapalkki a:hover {
  color: var(--color-text);
}

/* --- ehtomuutokset (domain-komponentti, #748) ---
   Lyhyt tiivistelma siita mika kayttoehdoissa / tietosuojaselosteessa muuttui.
   Naytetaan consent-ruudussa kun kayttajalta pyydetaan uusi hyvaksynta.
   Vasen tasaus koska tunnistautuminen-kortti on keskitetty — luettava kappale ei ole. */

.ehtomuutokset {
  text-align: left;
  background: var(--color-surface-low);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  margin-bottom: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.ehtomuutokset .muutos h3 {
  margin: 0 0 var(--space-1) 0;
}
.ehtomuutokset .muutos p {
  margin: 0 0 var(--space-1) 0;
  color: var(--color-muted);
}
.ehtomuutokset .muutos a {
  color: var(--color-muted);
  text-decoration: underline;
}
.ehtomuutokset .muutos a:hover {
  color: var(--color-text);
}

/* --- pisteet-teksti ---
   SVG text-elementti score-ring -komponentissa. */

.pisteet-teksti {
  font-family: var(--font-editorial);
  font-size: .625rem;
  font-weight: 700;
  fill: currentColor;
}

/* --- analyysijono ---
   Kelluva bottom-right UI: URL-syotto + jonon tilanne.
   Spec-mukainen: ei BEM, sisaiset elementit selektoreilla, variantit pisteella. */

.analyysijono {
  position: fixed;
  /* Mobiilissa selainten dynaamiset alapalkit peittavat komponentin alaosaa.
     Android Chrome palauttaa env(safe-area-inset-bottom) = 0 vaikka alapalkki
     peittaa nakymaa (palaute #47) — pelkka env()-summa ei siis riita, minimi
     on var(--space-5) = 2rem. iOS Safari: env ~34px → bottom ~58px, OK. */
  bottom: max(env(safe-area-inset-bottom, 0px) + var(--space-4), var(--space-5));
  right: var(--space-2);
  /* Vaakasuunta ankkuroidaan molemmista reunoista, ei `100vw`-leveydella (#1271).
     `100vw` sisaltaa klassisen vierityspalkin leveyden mutta `right` mittaa
     asiakaslaatikosta — erotus leikkasi vasenta reunaa (mitattu -7 px). */
  left: var(--space-2);
  /* Katto on pakollinen koska left+right -ankkurointi ottaa leveyden containing
     blockista: jos dokumentti ylivuotaa hetkellisesti latauksen aikana, ICB latchaa
     siihen eika palaudu, ja widget renderoityy ruudun ulkopuolelle. Mitattu /politiikka
     11.9.2026: leveys 894 px 360 px:n nakymassa, oikea reuna 542 px yli. Katolla 344 px.
     `100vw` on turvallinen tassa haarassa: saanto on mobiilissa (<=480 px) jossa ei ole
     klassista vierityspalkkia, ja liian iso arvo tekee katosta vain passiivisen — ei
     koskaan kapeampaa kuin nykyinen. Vrt. #1271 miksi 100vw:ta ei kayteta `right`in kanssa. */
  max-width: calc(100vw - 1rem);
  z-index: 50;
  font-size: 0.875rem;
  font-family: var(--font-label);
}
@media (min-width: 481px) {
  .analyysijono {
    bottom: var(--space-4);
    left: auto;          /* mobiilisaannon left kumotaan, muuten right jaa huomiotta */
    right: var(--space-4);
    width: 340px;
    max-width: calc(100vw - 2rem);
  }
}
.analyysijono.vilkkuu { animation: jono-vilkku 0.6s ease-out; }
@keyframes jono-vilkku {
  0%, 100% { box-shadow: none; }
  50% { box-shadow: 0 0 0 4px var(--color-accent); border-radius: var(--radius-md); }
}

.analyysijono .otsikko {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  margin-bottom: 0;
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  font-size: 0.875rem;
  font-weight: 600;
  transition: background var(--transition-fast);
}
.analyysijono .otsikko:hover { background: var(--color-primary-light); }
.analyysijono .otsikko .tila {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.analyysijono .otsikko .nuoli {
  font-size: 0.75rem;
  opacity: 0.7;
}

.analyysijono .runko {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-top: none;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  padding: var(--space-2);
  max-height: 320px;
  overflow-y: auto;
  box-shadow: var(--shadow-float);
}
.analyysijono form {
  display: flex;
  gap: var(--space-1);
  margin-bottom: var(--space-2);
}
/* Syottokentta: tyyppiagnostinen selektori tarkoituksella (#656).
   Kentta oli type="text" + inputmode="url" jotta skeematon syote ("yle.fi")
   paasee palvelimen normalisoitavaksi; input[type="url"]-sidonta putoaisi
   pois heti kun tyyppi vaihtuu. Lohkossa on vain yksi input.
   #674 palautti kentan type="url":ksi (mobiilin liitaehdotus) ja siirsi
   skeemattoman syotteen sallimisen lomakkeen novalidate-attribuutille.
   Juuri tyyppiagnostisuus teki tyypin vaihdosta turvallisen — tyylit
   osuvat edelleen. Ala sido selektoria tyyppiin. */
.analyysijono form input {
  flex: 1;
  padding: var(--space-2) var(--space-2);
  font-size: 0.875rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  margin: 0;
}
.analyysijono form input:focus {
  outline: 2px solid var(--color-outline);
  outline-offset: 1px;
}
.analyysijono .lisaa {
  padding: 0.25rem 0.625rem;
  margin-bottom: 0;
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1;
  background: var(--color-accent);
  color: var(--color-text-inverse);
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  width: auto;
  min-width: 0;
  flex: 0 0 auto;
}
.analyysijono .lisaa:hover { background: var(--color-accent-light); }
.analyysijono .virhe {
  color: var(--color-danger);
  font-size: 0.75rem;
  margin-bottom: var(--space-2);
  padding: 0 var(--space-1);
}
/* Positiivinen ohjausviesti (.virhe:n rinnakkainen, neutraali ei-punainen).
   Esim. media-URL → koostesivulle (discover-redirect). Sama kompakti koko kuin
   .virhe — mahtuu analyysijonon ahtaaseen UI:hin. Sävy = primary-subtle
   (sama kuin .ilmoitus.info, koherenssi info-laatikoiden kanssa). */
.analyysijono .ohjaus {
  color: var(--color-text);
  background: var(--color-primary-subtle);
  border-left: 3px solid var(--color-primary);
  font-size: 0.75rem;
  margin-bottom: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
}

.analyysijono .tyo {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-muted);
}
.analyysijono .tyo:last-child { border-bottom: none; }
.analyysijono .tyo .ikoni {
  flex-shrink: 0;
  width: 1.25rem;
  text-align: center;
}
.analyysijono .tyo .ikoni.valmis { color: var(--color-success); }
.analyysijono .tyo .ikoni.epaonnistunut { color: var(--color-danger); }
.analyysijono .tyo .info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.analyysijono .tyo .nimi {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 0.8125rem;
}
.analyysijono .tyo .tila {
  font-size: 0.75rem;
  color: var(--color-muted);
}
.analyysijono .tyo .aika-arvio {
  display: block;
  font-size: 0.75rem;
  color: var(--color-muted);
  margin-top: 2px;
}
.analyysijono .tyo .toiminnot {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}
.analyysijono .tyo .sulje {
  opacity: 0.5;
  font-size: 1rem;
  padding: 0 var(--space-1);
}
.analyysijono .tyo .sulje:hover { opacity: 1; }

/* #958: kertoo montako jonon tyota jaa naytetyn listan ulkopuolelle.
   Ei omaa font-sizea tarkoituksella — font_size_max-kynnys on 232 ja taso 231
   (css-velka-merkit.json: "EI NOSTETA enaa"), ja lohkon oma 0.875rem riittaa.
   Vaimennus tehdaan varilla, ei koolla. */
.analyysijono .jono-otos {
  margin: var(--space-1) 0 0;
  color: var(--color-muted);
}

.analyysijono .tyhjenna {
  display: block;
  width: 100%;
  text-align: center;
  margin-top: var(--space-1);
  font-size: 0.75rem;
  color: var(--color-muted);
}

/* --- analyysijono progress-bar ---
   Task #312 (paketti B 2c): processing/pending-jobien arvio.
   Inline-tyyli sallittu vain --edistyma-CSS-custom-propertyna (Type-C, frontend-css.md). */
.analyysijono .tyo .edistyma-palkki {
  height: 3px;
  background: var(--color-surface-mid);
  border-radius: var(--radius-sm);
  margin-top: 2px;
  overflow: hidden;
}
.analyysijono .tyo .edistyma-palkki .osa {
  background: var(--color-accent);
  height: 100%;
  width: var(--edistyma, 0%);
  transition: width 0.3s ease;
}

.spinner.pieni {
  width: 0.875rem;
  height: 0.875rem;
  border-width: 1.5px;
}
.spinner.spinner-keskitetty {
  display: block;
  margin-left: auto;
  margin-right: auto;
}

/* --- skeleton ---
   Latausanimaatio. Perustila = gradient-shimmer. Text-muunnos = rivipalikka.
   Block/line = artikkelisivun isommat placeholderit. */
.skeleton {
  background: linear-gradient(90deg, var(--color-surface-mid) 25%, var(--color-skeleton) 50%, var(--color-surface-mid) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.5s infinite;
  border-radius: var(--radius-md);
}
@keyframes skeleton-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.skeleton-text { height: 1em; margin-bottom: var(--space-2); }
/* Width-variantit (#383, 6.5.2026) — sama nimimaailma kuin .skeleton-line.* */
.skeleton-text.lyhyt { width: 40%; }
.skeleton-text.keski { width: 60%; }
.skeleton-text.pitka { width: 90%; }
.skeleton-text.tayte { width: 100%; }
.skeleton-block { padding: var(--space-3) 0; }
.skeleton-line {
  background: var(--color-surface-mid);
  border-radius: var(--radius-sm);
  height: 0.9rem;
  animation: skeleton-pulse 1.5s ease-in-out infinite;
}
/* Width-variantit (#383, 6.5.2026) — staattiset placeholder-leveydet, korvaavat
   inline width:X% -tyylit. Vapaat leveydet (esim. 45%, 65%, 85%) tehdään
   yhdistelmillä lyhyt+keski+pitka kun pixel-tarkkuus ei ole tärkeää. */
.skeleton-line.lyhyt  { width: 40%; }
.skeleton-line.keski  { width: 60%; }
.skeleton-line.pitka  { width: 90%; }
.skeleton-line.tayte  { width: 100%; }
/* Otsikkorivin korkeusvariantti — käytetään ennen leipäteksti-skeletoneita. */
.skeleton-line.otsikko    { height: 1.5rem; }
.skeleton-line.alaotsikko { height: 1.2rem; }
@keyframes skeleton-pulse {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 0.8; }
}

/* --- spinner ---
   Pyoriva latausindikaattori. */
.spinner {
  display: inline-block;
  width: 1.5rem;
  height: 1.5rem;
  border: 2px solid var(--color-border);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* --- latausindikaattori ---
   Spinneri ja selite samalla rivilla (#810, palaute #135). Komponentti:
   kehys/Views/components/latausindikaattori.php. Selite on muted koska se on
   valiaikainen tilateksti, ei sisaltoa. Ei omaa font-sizea: koko peritaan
   kontekstista, jolloin sama komponentti toimii sivun leipatekstissa ja
   napissa ilman variantteja. */
.latausindikaattori {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-muted);
}
/* Spinneri on kiintean kokoinen koriste, selite on joustava osa. Ilman tata
   spinneri on flex-itemina `flex-shrink: 1` ja KUTISTUU VAIN LEVEYSSUUNNASSA
   kun selite rivittyy — ympyrasta tulee ellipsi. Mitattu 2.9.2026
   360px-viewportissa: 11.67 x 14.00 px. Ei nakynyt ennen kuin
   `.koostehaku-tulokset`in `white-space: normal` salli rivityksen (siihen asti
   kaare oli max-content-levyinen eika kutistunut). */
.latausindikaattori .spinner {
  flex-shrink: 0;
}

/* --- tekstikorostukset (propagandatekniikat) ---
   mark-elementti data-type-attribuutilla — visuaalinen osoitus
   analyysin loytamista framing/loaded/omission-osioista. */
mark[data-type="framing"] {
  background: var(--color-highlight-framing);
  padding: 0.05em 0.2em;
  border-radius: 2px;
}
mark[data-type="loaded-language"] {
  background: var(--color-highlight-loaded);
  padding: 0.05em 0.2em;
  border-radius: 2px;
}
mark[data-type="omission"] {
  background: var(--color-highlight-omission);
  padding: 0.05em 0.2em;
  border-radius: 2px;
}

/* --- toolbar + dropdown ---
   Geneerinen toolbar-rivi + pudotusvalikko (artikkeli-tyokalupalkki kaavio).
   Englanninkielinen nimeamien sailytetty taman taskin scopen ulkopuolella. */
.toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.dropdown { position: relative; display: inline-block; }
.dropdown-menu {
  position: absolute;
  right: 0;
  top: 100%;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-float);
  padding: var(--space-1) 0;
  min-width: 180px;
  z-index: 50;
}
.dropdown-item {
  display: block;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  text-align: left;
  border: none;
  background: none;
  font-size: 0.875rem;
  cursor: pointer;
  color: var(--color-text);
  text-decoration: none;
}
.dropdown-item:hover { background: var(--color-surface-mid); }

/* --- neutral-content + briiffi-sisalto (LLM:n tuottama sanitoitu proosa) ---
   Kaksi nimea, yhdet deklaraatiot. Neutraali versio ja kansalaisbriiffi (#779)
   renderoivat samanlaista markdown-proosaa, joten tyylit jaetaan
   SELEKTORIRYHMALLA eika kopiona (sama malli kuin `.painike.ensisij,
   .painike.maksullinen`, #747) — kopio ajautuisi erilleen ensimmaisessa
   muutoksessa. Oma luokka briiffille silti siksi, etta `grep .briiffi-sisalto`
   loytaa sen kayttopaikat ja mahdollinen briiffikohtainen saanto on
   lisattavissa yhteen paikkaan.
   Briiffin otsikkotaso on <h2> (Parsedown "## Mita jutussa ei kerrota"), joten
   se liitetaan h1:n deklaraatioon — uutta font-size-arvoa EI lisata (kynnys
   232, taso 230, konfigissa "EI NOSTETA enaa"). */
.neutral-content h1,
.neutral-content h2,
.briiffi-sisalto h1,
.briiffi-sisalto h2 { font-size: 1.5rem; margin-bottom: var(--space-3); }
.neutral-content p,
.briiffi-sisalto p { margin-bottom: var(--space-3); line-height: 1.6; }
.neutral-content strong,
.briiffi-sisalto strong { font-weight: 600; }
.neutral-content ul,
.neutral-content ol,
.briiffi-sisalto ul,
.briiffi-sisalto ol { margin-bottom: var(--space-3); padding-left: var(--space-4); }
.neutral-content li,
.briiffi-sisalto li { margin-bottom: var(--space-1); }
/* Briiffissa on kaksi osiota perakkain — toinen otsikko tarvitsee ilmavuutta
   edellisesta kappaleesta, ensimmainen ei paneelin karjessa. */
.briiffi-sisalto h2 { margin-top: var(--space-4); }
.briiffi-sisalto h2:first-child { margin-top: 0; }

/* --- lahdeviite (briiffin viitelinkki) ---
   Malli tuottaa perakkaiset lahdeviitteet ilman valimerkkia, joten
   SafeMarkdownRenderer lisaa ankkurien valiin valilyonnin (#1218, palaute #259).
   Tama lohko tekee eron myos nakyvaksi: viite on lahdemerkinta, ei osa lausetta.
   Scopettamaton luokka koska sama renderoija palvelee myos neutral_versionia ja
   paivatiivistelmaa, joilla on eri kontti (spec/frontend-css.md § periaate 2).
   font-size on TIETOISESTI jatetty pois: kynnys 232 on "EI NOSTETA enaa"
   (test/css-velka-merkit.json § font_size_max). Ero tehdaan taustalla ja valilla. */
.lahdeviite {
  margin-left: var(--space-1);
  padding: 0 var(--space-1);
  border-radius: var(--radius-sm);
  background: var(--color-surface-mid);
  color: var(--color-muted);
  text-decoration: none;
  white-space: nowrap;
}
.lahdeviite:hover { color: var(--color-text); text-decoration: underline; }

/* --- content-body (pienempi leipateksti, esim. drilldown-tiivistelmat) --- */
.content-body p { font-size: 0.875rem; line-height: 1.6; }
.content-body ul {
  font-size: 0.875rem;
  padding-left: var(--space-4);
  margin: 0;
}

/* --- osio-lista (artikkelin osiot, luetelmat) --- */
.osio-lista {
  list-style: disc;
  padding: 0;
  margin: 0;
  padding-left: var(--space-4);
}
.osio-lista li {
  padding: 0;
  border-bottom: 1px solid var(--color-border);
}
.osio-lista li:last-child { border-bottom: none; }
ol.osio-lista { list-style: decimal; }

/* --- tag (yksittainen tunniste-pill) + tag-cloud ---
   Pyoristetty pilleri. Kaytetaan tag-cloud-containerissa (esim. etusivun suodattimet,
   drilldown-narratiivit). Primary-fixed = desaturoitu navy. */
.tag {
  display: inline-flex;
  align-items: center;
  padding: 0.15em 0.6em;
  border-radius: var(--radius-full);
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 0.75rem;
  background: var(--color-tag-bg);
  color: var(--color-primary);
  transition: all var(--transition-fast);
}
.tag:hover { box-shadow: var(--shadow-sm); }
.tag-cloud {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

/* --- bias-gauge (vinoumamittari — visuaalinen kaari tai palkki) --- */
.bias-gauge { text-align: center; }

/* --- blackout-prompt (pimento-kehotus, artikkelisivun ennen pimento-tuloksia) --- */
.blackout-prompt {
  margin-top: var(--space-3);
  padding: var(--space-4);
  background: var(--color-accent-subtle);
  border: 1px solid var(--color-accent-border);
  border-radius: var(--radius-md);
}

/* --- entiteetti-ruudukko (2-sarakkeinen grid artikkelin toimijoille/entiteeteille) --- */
.entiteetti-ruudukko {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 481px) {
  .entiteetti-ruudukko { grid-template-columns: 1fr 1fr; }
}

/* --- taho-linkki (kritisoidut/puolustetut tahot → /aihe/{slug}) ---
   Sama linkkityyli kuin pohjan a-elementti (color: --color-primary, hover:
   --color-primary-light). Plain-text (resolvoimaton) perii osio-lista li
   -tyylin sellaisenaan — ei eroa staattisten ja "klikkaamattomien" valilla.
   Hover-tila paljastaa klikattavuuden. (#404, sama UX-paatos kuin kamma-kortti #308.) */
.taho-linkki {
  color: var(--color-primary);
  text-decoration: underline;
}
.taho-linkki:hover {
  color: var(--color-primary-light);
}

/* --- trending-tags (etusivu — toistuvat aiheet) --- */
.trending-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding-bottom: var(--space-2);
}
.trending-tags .merkki {
  white-space: nowrap;
  flex-shrink: 0;
  cursor: pointer;
  border: none;
  font-family: inherit;
  transition: background 0.15s, color 0.15s;

  margin-bottom: 0;
}
.trending-tags .merkki:hover { opacity: 0.85; }

/* --- osio-alaotsikko (etusivun pienotsikko) --- */
.osio-alaotsikko {
  font-family: var(--font-label);
  font-size: 0.875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-muted);
  margin-bottom: var(--space-2);
}

/* --- summary-narratives-title (drilldown-narratiivi otsikko) --- */
.summary-narratives-title {
  font-size: 0.8rem;
  color: var(--color-muted);
  margin-top: var(--space-3);
  margin-bottom: var(--space-1);
}

/* --- narratiivipaneeli (topic, media, haku) ---
   Koko paneelin tyylit yhdessa lohkossa: ohjaimet (ajanjakso-napit
   poistettu #1418 — aika tulee sivun rajauksesta), skeleton, meta-rivi, luotettavuus-badge, kortti + sen badget,
   savy-badge ja sen 6 variantia, agenttitagit 3 varianttia, sokeat
   kohdat ja media-profiili. */
.narrative-controls {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.narrative-skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.narrative-skeleton .skeleton-block {
  height: 5rem;
  background: linear-gradient(90deg, var(--color-surface-low) 25%, var(--color-surface) 50%, var(--color-surface-low) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.5s infinite;
  border-radius: var(--radius-md);
}

.narrative-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.reliability-badge {
  display: inline-block;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-full);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.reliability-low {
  background: var(--color-bias-high-bg);
  color: var(--color-bias-high-text);
}
.reliability-moderate {
  background: var(--color-bias-medium-bg);
  color: var(--color-bias-medium-text);
}
.reliability-high {
  background: var(--color-bias-low-bg);
  color: var(--color-bias-low-text);
}

.narratiivi-kortti {
  background: var(--color-secondary);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
}
.narratiivi-kortti .otsikkorivi {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  flex-wrap: wrap;
}
.narratiivi-kortti .merkit {
  display: flex;
  gap: var(--space-1);
  flex-wrap: wrap;
  align-items: center;
}
.narrative-label {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--color-text);
  font-family: var(--font-body);
}
.narrative-examples {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: var(--space-1);
  border-top: 1px solid var(--color-border);
  margin-top: var(--space-2);
}
.narrative-examples span,
.narrative-examples a,
.narrative-examples strong { font-size: 0.75rem; color: var(--color-muted); font-style: italic; }
/* Esimerkkiartikkelien linkit (#648) — otsikko klikattavana /artikkeli/{url_hash}:iin. */
.narrative-examples a:hover { color: var(--color-primary); text-decoration: underline; }
.narrative-examples strong { font-style: normal; color: var(--color-text); }

.tone-badge {
  display: inline-block;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-full);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.tone-pelottava    { background: var(--color-bias-high-bg);   color: var(--color-bias-high-text); }
.tone-toiveikas    { background: var(--color-bias-low-bg);    color: var(--color-bias-low-text); }
.tone-neutraali    { background: var(--color-bias-neutral-bg); color: var(--color-bias-neutral-text); }
.tone-moralisoiva  { background: var(--color-tone-moralisoiva-bg);  color: var(--color-tone-moralisoiva-text); }
.tone-dramatisoiva { background: var(--color-tone-dramatisoiva-bg); color: var(--color-tone-dramatisoiva-text); }
.tone-kriittinen   { background: var(--color-tone-kriittinen-bg);   color: var(--color-tone-kriittinen-text); }

.agent-tags {
  display: flex;
  gap: var(--space-1);
  flex-wrap: wrap;
}
.agent-tag {
  display: inline-block;
  padding: 2px var(--space-2);
  border-radius: var(--radius-full);
  font-size: 0.7rem;
  font-weight: 500;
}
.agent-tag.threat { background: var(--color-bias-high-bg);  color: var(--color-bias-high-text); }
.agent-tag.victim { background: var(--color-victim-bg);     color: var(--color-victim-text); }
.agent-tag.savior { background: var(--color-bias-low-bg);   color: var(--color-bias-low-text); }

.blind-spots {
  background: var(--color-secondary);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
}
/* Katvealueet-pillit ovat LLM:n tuottamia pitkia fraaseja (32-62 merkkia) —
   .merkki-peruksen nowrap aiheuttaisi sivuttaisvierityksen mobiilissa
   (palaute #44). Sallitaan rivitys vain tassa kontekstissa. */
.blind-spots .merkki { white-space: normal; overflow-wrap: anywhere; }
.media-profile {
  background: var(--color-secondary);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  border: 1px solid var(--color-border);
}

/* Wrapper-luokka narratiivipaneelille — sitoo task #248:ssa siirretyt tyylit
   (korvaa aiemmin inline-kaytetyt text-sm/text-xs/muted-text -utilityt). */
.narratiivi-paneeli .tyhja-vinkki { font-size: 0.875rem; color: var(--color-muted); }
.narratiivi-paneeli .narrative-meta span { font-size: 0.875rem; color: var(--color-muted); }
.narratiivi-paneeli .narratiivi-kortti p { font-size: 0.875rem; color: var(--color-muted); }
.narratiivi-paneeli .blind-spots strong { font-size: 0.875rem; }
.narratiivi-paneeli .media-profile strong { font-size: 0.875rem; }
.narratiivi-paneeli .media-profile p { font-size: 0.875rem; }

/* --- debug-dumppi ---
   Diagnostiikkalohko artikkelisivulla (?debug=1). Tietoinen inline-toteutus
   yhden viewin tasolla — EI komponentti, koska käyttöpaikka on yksi
   eikä tarvitse abstrahointia. Ks. spec/komponentit.md § Inline-duplikaatit
   "Debug-dumpit". CSS täällä jotta inline-styles minimissä viewissä. */
.debug-dumppi { background: var(--color-surface); }
.debug-dumppi .debug-dumppi-otsikko { margin-top: 0; font-size: 0.95rem; color: var(--color-muted); }
.debug-dumppi .debug-dumppi-osio { margin-top: var(--space-2); }
.debug-dumppi .debug-dumppi-osio summary {
    cursor: pointer;
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--color-muted);
    padding: var(--space-1) 0;
}
.debug-dumppi .debug-dumppi-osio[open] summary { color: var(--color-text); }
.debug-dumppi h4 {
    font-family: var(--font-body);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--color-muted);
    margin: var(--space-3) 0 var(--space-1);
}
.debug-dumppi .debug-dumppi-lista {
    display: grid;
    grid-template-columns: minmax(160px, max-content) 1fr;
    gap: var(--space-1) var(--space-3);
    margin: var(--space-2) 0;
    font-size: 0.8125rem;
}
.debug-dumppi .debug-dumppi-lista dt {
    font-family: var(--font-label);
    font-weight: 600;
    color: var(--color-muted);
}
.debug-dumppi .debug-dumppi-lista dd {
    margin: 0;
    word-break: break-word;
    font-family: var(--font-body);
}
.debug-dumppi .debug-dumppi-lista dd em { color: var(--color-muted); font-style: italic; }
.debug-dumppi .debug-dumppi-pre {
    background: var(--color-bg);
    padding: var(--space-2);
    font-size: 0.75rem;
    line-height: 1.4;
    overflow-x: auto;
    max-height: 400px;
    overflow-y: auto;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    margin: 0 0 var(--space-2);
    white-space: pre-wrap;
    word-break: break-word;
}
.debug-dumppi .debug-dumppi-pre.debug-dumppi-pre-pitka { max-height: 600px; }
.debug-dumppi .debug-dumppi-tagit {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    list-style: none;
    padding: 0;
    margin: var(--space-1) 0 var(--space-3);
    font-size: 0.75rem;
}
.debug-dumppi .debug-dumppi-tagit li {
    list-style: none;
    background: var(--color-bg);
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    font-family: var(--font-label);
}

/* ============================================================
   7. APU-UTILITIES — rajattu whitelist (D38, 21.4.2026)
   Sallitut: tila, layout-spacing, flex-rakenne.
   Tekstityyli, värit ja linkit kuuluvat komponenttiin — ei tänne.
   Ks. spec/frontend-css.md § Sallitut utility-luokat.
   ============================================================ */

/* --- tila --- */
.hidden  { display: none; }
.muted   { color: var(--color-muted); }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.lataa   { opacity: .5; pointer-events: none; transition: opacity var(--transition-fast); }

/* --- marginaali (ylä) --- */
.mt-0   { margin-top: 0; }              /* #383, 6.5.2026 — modaalien h3-otsikot */
.mt-xs  { margin-top: var(--space-1); }
.mt-sm  { margin-top: var(--space-2); }
.mt-md  { margin-top: var(--space-3); }
.mt-lg  { margin-top: var(--space-4); }
.mt-xl  { margin-top: var(--space-5); }
.mt-2xl { margin-top: var(--space-6); }

/* --- marginaali (ala) --- */
.mb-0   { margin-bottom: 0; }           /* #383, 6.5.2026 — viimeisen elementin tyhjä alapuoli */
.mb-xs  { margin-bottom: var(--space-1); }
.mb-sm  { margin-bottom: var(--space-2); }
.mb-md  { margin-bottom: var(--space-3); }
.mb-lg  { margin-bottom: var(--space-4); }
.mb-xl  { margin-bottom: var(--space-5); }

/* --- flex-layout --- */
.flex-wrap    { flex-wrap: wrap; }
.items-center { align-items: center; }
.gap-sm       { gap: var(--space-2); }
.gap-md       { gap: var(--space-3); }
.gap-lg       { gap: var(--space-4); }   /* #383, 6.5.2026 — profile.php hintataulukoiden välistys */
.flex-1       { flex: 1; }
.w-full       { width: 100%; }

/* --- text-tasaus (rakenteellinen, ei typografinen) ---
   D38-jatko (#383, 6.5.2026): poikkeus säännöstä "tekstityyli kuuluu komponenttiin".
   Käyttötapa on data/rakenne (taulukon hinta-sarake, kortin keskittäminen) — ei
   typografinen ominaisuus. 12+ osumaa systemaattinen tarve. */
.text-center  { text-align: center; }
.text-right   { text-align: right; }

/* --- sivurakenne-apuri (otsikot, sivupalkin rivit) --- */
.sidebar-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
}
.osio-otsikko     { font-size: 1.25rem; margin-bottom: var(--space-3); }
.valiotsikko { font-size: 0.9rem;  color: var(--color-muted); margin-bottom: var(--space-2); }
.sidebar-title    { font-size: 1rem;    margin-bottom: var(--space-3); }
.osio-mb          { margin-bottom: var(--space-5); }
.page-header      { margin-bottom: var(--space-5); }
.page-header h1   { margin-bottom: var(--space-1); }

/* --- grid-apurit --- */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--space-3);
}

/* Aihe-arkiston tunnusluvut: kolme numeroa (artikkelit, mediat, vinouma) AINA
   rinnakkain — myös mobiilissa. Kortit ovat lukuvaltaisia (numero + lyhyt
   label), joten 3 saraketta toimii ~360px:n leveydellä kun gap ja paddingit
   ovat tiukkoja. Kortin ja numerofontin kutistaminen ehkäisee overflowin. */
.korttiruudukko.stats-grid-3 {
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
}
.korttiruudukko.stats-grid-3 .kortti { padding: var(--space-2); }
.korttiruudukko.stats-grid-3 .entiteetti-tilasto .arvo { font-size: 1.25rem; }
@media (min-width: 768px) {
  .korttiruudukko.stats-grid-3 { gap: var(--space-3); }
  .korttiruudukko.stats-grid-3 .kortti { padding: var(--space-3); }
  .korttiruudukko.stats-grid-3 .entiteetti-tilasto .arvo { font-size: 1.5rem; }
}

/* ============================================================
   === Kamma-kortti ===
   Task #308 (29.4.2026), yleistetty #881 (16.8.2026). Domain-komponentti:
   .kamma-kortti — "Kritiikki ja puolustus" -kampakuvaaja neljalla sivulla
   (/media, /aihe, /haku, /kirjoittaja). #881 poisti rinnakkaisen
   .kuka-kritisoi-puolustaa-lista -toteutuksen (aihe- ja hakusivun listat).
   Käyttää olemassa olevia tokeneita --color-kritisoitu-* ja --color-puolustettu-*.
   (Ei viistoa tahtimerkin perassa: merkkipari sulkisi taman kommentin kesken,
   jolloin lopputeksti luettaisiin selektoriksi ja seuraava saanto katoaisi.)
   Mobile-first.
   ============================================================ */

/* --- Kamma-kortti --- */
/* Leveysrajaus (palaute #220). Poikkeaa tietoisesti spec/frontend-css.md:n
   saannosta "komponentin leveys tayttaa aina aitinsa": kamma on kuvaaja, ei
   tekstilohko, ja sen luettavuus rikkoutuu leveana — lukija vertaa rivin
   vasenta (kritiikki) ja oikeaa (puolustus) laitaa toisiinsa, ja 1400px:n
   levealla lohkolla laidat ovat liian kaukana toisistaan jotta vertailu
   syntyisi. Rajaus on TASSA eika neljassa kutsujassa (/media, /aihe, /haku,
   /kirjoittaja), koska muuten sama saanto pitaisi toistaa nelja kertaa ja
   viides kayttopaikka unohtaisi sen.
   MOBIILI: tama on max-width, joten se ei voi sitoa kapealla naytolla — 360px:n
   ruudulla kortti on taysleveä eika 720px:n raja aktivoidu lainkaan. Siksi
   saanto on perustyylissa eika media queryssa (sama mobile-first-kuvio kuin
   `.hero > *`, ks. kommentti sen yhteydessa). Ei `margin: 0 auto`: kortin vasen
   reuna pysyy linjassa ylapuolisen osio-otsikon kanssa. */
.kamma-kortti {
  max-width: var(--content-kuvaaja);
}
.kamma-kortti .kamma-mittari {
  font-size: 0.875rem;
  color: var(--color-muted);
  margin-bottom: var(--space-3);
}
.kamma-kortti .kamma-mittari-krit { color: var(--color-kritisoitu-text); }
.kamma-kortti .kamma-mittari-puol { color: var(--color-puolustettu-text); }

.kamma-kortti .kamma-rivit {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* Pico-luodin vaimennus <li>-tasolla (vahti: --group=frontend). */
.kamma-kortti .kamma-rivit li { list-style: none; }

/* Rivirakenne (4 saraketta vasemmalta oikealle):
   [nimi] [crit-luku] [palkki: vasen | oikea] [def-luku]
   Nimi on rivin alussa, palkki keskellä rivin oikealla puolella ja kasvaa
   keskeltä haarautuen — vasen ulottuu vasemmalle, oikea oikealle.

   `:not([hidden])` on speksin vakiintunut kuvio (ks. `.sivutus` ja
   `.hakukentta > input:not([hidden])`, frontend-css.md § Picon lomakesaannot):
   ilman sita tama `display: grid` kumoaisi selaimen `[hidden] { display: none }`
   -oletuksen, koska tekijan saanto voittaa selaimen oletuksen spesifisyydesta
   riippumatta. #945: app.js piilottaa `$naytaAluksi`-rajan ylittavat rivit
   `hidden`-attribuutilla ja paljastaa ne napista — piilotus ei saa vuotaa auki
   tasta saannosta. Ei `!important`. */
.kamma-kortti .kamma-rivi:not([hidden]) {
  display: grid;
  grid-template-columns: minmax(5rem, 35%) 2rem 1fr 2rem;
  align-items: center;
  gap: var(--space-1);
  font-size: 0.85rem;
}

.kamma-kortti .kamma-arvo-vasen,
.kamma-kortti .kamma-arvo-oikea {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.kamma-kortti .kamma-arvo-vasen {
  text-align: right;
  color: var(--color-kritisoitu-text);
}
.kamma-kortti .kamma-arvo-oikea {
  text-align: left;
  color: var(--color-puolustettu-text);
}
/* #881: luku voi olla drilldown-linkki (aihesivun entity_stance-suodatus).
   Vari tulee ylla olevista saannoista — linkille vain affordanssi hoverissa. */
.kamma-kortti a.kamma-arvo-vasen,
.kamma-kortti a.kamma-arvo-oikea {
  text-decoration: none;
}
.kamma-kortti a.kamma-arvo-vasen:hover,
.kamma-kortti a.kamma-arvo-oikea:hover {
  text-decoration: underline;
}

/* Vaakapalkki, kaksi puolikasta keskeltä haarautuen.
   Vasen: justify-self end → palkki päättyy keskelle ja kasvaa vasemmalle.
   Oikea: justify-self start → palkki alkaa keskeltä ja kasvaa oikealle. */
.kamma-kortti .kamma-palkki {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  min-height: 1.25rem;
}

.kamma-kortti .kamma-palkki-vasen,
.kamma-kortti .kamma-palkki-oikea {
  display: block;
  height: 0.5rem;
  border-radius: 0;
  max-width: 100%;
}
.kamma-kortti .kamma-palkki-vasen {
  background: var(--color-kritisoitu-bg);
  border: 1px solid var(--color-kritisoitu-text);
  justify-self: end; /* päättyy keskelle, kasvaa vasemmalle */
}
.kamma-kortti .kamma-palkki-oikea {
  background: var(--color-puolustettu-bg);
  border: 1px solid var(--color-puolustettu-text);
  justify-self: start; /* alkaa keskeltä, kasvaa oikealle */
}

.kamma-kortti .kamma-nimi {
  text-align: left;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.kamma-kortti a.kamma-nimi {
  color: var(--color-text);
  text-decoration: none;
  border-bottom: 1px dotted var(--color-border);
}
.kamma-kortti a.kamma-nimi:hover {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
}

/* #881 (palaute #179): tasapainoinen rivi merkitaan, EI suodateta pois.
   Merkinta on tekstia omalla rivillaan (grid-column 1 / -1) — vari yksin ei
   vality ruudunlukijalle eika nelisarakkeiseen riviin mahdu viidetta saraketta
   ilman etta nimi tai palkki kutistuu. Ei omaa font-sizea: perii rivin koon
   (mobiili 0.85rem / >=480px 0.95rem), joten mitaan ei tarvitse pitaa synkassa. */
.kamma-kortti .kamma-tasapaino {
  grid-column: 1 / -1;
  color: var(--color-muted);
  font-family: var(--font-label);
}

/* #945 (palaute #214): "Lataa lisaa" -nappi. Ulkoasu tulee `.painike`-luokasta,
   tassa vain sijoittelu — ei omaa font-sizea (kynnys 232, css-velka-merkit.json).

   ⚠️ `[hidden]`-rajaus on PAKOLLINEN eika kosmetiikkaa: `.painike` asettaa
   `display: inline-flex` (app.css osio "painike (geneerinen)"), joka kumoaa
   selaimen `[hidden] { display: none }` -oletuksen samalla mekanismilla kuin
   `.kamma-rivi`-grid yllä. Ilman tata nappi nakyisi ilman JS:aa eika tekisi
   mitaan — juuri se kontrolli jonka #912 kielsi. Kirjoitettu `[hidden]`-
   nollauksena eika `:not([hidden])`-rajauksena koska `display` tulee toisesta
   luokasta jota tama saanto ei omista; (0,2,1) voittaa `.painike`n (0,1,0). */
.kamma-kortti .kamma-lisaa[hidden] { display: none; }
.kamma-kortti .kamma-lisaa {
  margin-top: var(--space-3);
}

.kamma-kortti .info-teksti {
  margin-top: var(--space-3);
  margin-bottom: 0;
}

@media (min-width: 480px) {
  .kamma-kortti .kamma-rivi {
    grid-template-columns: minmax(8rem, 12rem) 3rem 1fr 3rem;
    gap: var(--space-2);
    font-size: 0.95rem;
  }
}

/* ============================================================
   === Kehystysroolit (task #504) ===
   Mediasivun "Kehystysroolit"-palikka narratiivit-tabissa: kolme
   rinnakkaista top-listaa (uhka/uhri/pelastaja). Domain-komponentti.
   Käyttää olemassa olevia .agent-tag.threat/victim/savior -värejä
   rooliotsikoissa. Ei uusia font-size-arvoja — periytyy (#504-rajoite).
   Mobile-first: 1 sarake → 600px:stä 3 saraketta.
   ============================================================ */
.kehystysroolit h3 {
  margin-top: 0;
  margin-bottom: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.kehystysroolit-ruudukko {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
/* Grid-itemin min-width:auto esti pitkän nimen kutistumisen: .kehystysroolit-nimi
   ellipsoituu vasta kun sarake saa kutistua sisältönsä alle (#323, mitattu 6.9.2026). */
.kehystysroolit-sarake {
  min-width: 0;
}
.kehystysroolit-sarake h4 {
  margin: 0 0 var(--space-2);
}
.kehystysroolit-lista {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
/* Pico-luodin vaimennus <li>-tasolla (vahti: --group=frontend). */
.kehystysroolit-lista li { list-style: none; }
.kehystysroolit-rivi {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
}
.kehystysroolit-rivi:last-child {
  border-bottom: none;
}
/* Kaksirivinen rivi (#1299, palaute #327): nimi + pääluku ylärivillä, meta ja
   absoluutti/n alarivillä. Sijoittelu on EKSPLISIITTINEN eikä auto-placement:
   meta on valinnainen, ja ilman sitä auto-placement siirtäisi oikean sarakkeen
   luvun vasempaan. Ei uusia font-size-arvoja — alarivin koko ja väri tulevat
   .info-teksti-luokalta markupissa (#504-rajoite). */
/* Sarakkeen 2 ALARAJA + arvon oikea tasaus (palaute #350, 8.9.2026:
   "tasaa prosenttiluvut oikeaan reunaan").

   MIKSI ALARAJA: jokainen `.kehystysroolit-rivi` on OMA grid-konttinsa, joten
   `auto` mitoittuu rivin omaan sisaltoon eivatka rivien luvut osu samaan
   linjaan ("6 %" ja "12 %" eri kohdissa). `minmax(3.5rem, auto)` antaa
   sarakkeelle yhteisen pohjan, jolla tavallinen sisalto (`60 %` ylarivilla,
   `59 / 99` alarivilla, mitattu tuotannosta 6.9.2026) mahtuu ilman kasvua ->
   rivit linjautuvat. `auto`-ylapaa jaa paikalleen, koska alarivin
   `.kehystysroolit-tarkenne` on LEVEAMPI kuin prosentti ja `nowrap`: kiintea
   `width` katkaisisi sen tai vuotaisi ulos. Kompromissi on tietoinen —
   poikkeuksellisen leveä `count / n` kasvattaa oman rivinsa sarakkeen eika se
   linjaudu. Taysi rivien valinen linjaus vaatisi `subgrid`in lista-tasolla,
   mika muuttaisi myos yksirivista varianttia viidessa muussa kutsupaikassa.

   Yksirivinen (flex-)variantti EI muutu: siella `justify-content: space-between`
   tyontaa arvon oikeaan reunaan ja laatikko kuroutuu sisaltoonsa, joten
   `text-align: right` on siella no-op. */
.kehystysroolit-rivi.kaksirivinen {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(3.5rem, auto);
  column-gap: var(--space-2);
}
.kehystysroolit-rivi.kaksirivinen .kehystysroolit-nimi { grid-column: 1; grid-row: 1; }
.kehystysroolit-rivi.kaksirivinen .kehystysroolit-arvo { grid-column: 2; grid-row: 1; }
/* Meta saa katketa: se on juuri se rivi ("Wille Rydman · perussuomalaiset ·
   ministeri") joka mitattiin vuotamaan 345 px:n viewportissa (#1294 / palaute
   #323). Nimi säilyttää ellipsinsä, meta kietoutuu. */
.kehystysroolit-rivi.kaksirivinen .kehystysroolit-meta {
  grid-column: 1;
  grid-row: 2;
  min-width: 0;
  overflow-wrap: anywhere;
}
.kehystysroolit-rivi.kaksirivinen .kehystysroolit-tarkenne {
  grid-column: 2;
  grid-row: 2;
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.kehystysroolit-nimi {
  text-decoration: none;
  color: var(--color-text);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
a.kehystysroolit-nimi:hover {
  color: var(--color-accent);
  text-decoration: underline;
}
.kehystysroolit-arvo {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--color-muted);
  white-space: nowrap;
  text-align: right;   /* palaute #350 — vaikuttaa gridissa, no-op flex-variantissa */
}
@media (min-width: 600px) {
  .kehystysroolit-ruudukko {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

/* ============================================================
   === Tunneprofiili (task #509) ===
   Entiteettisivun (media/kirjoittaja/aihe) tunneprofiili-palikka:
   yksi top-lista (emotional.appeals) label + lukumäärä + osuus.
   Data-ohjattu domain-komponentti (ketju #511 uusiokäyttää).
   Ei uusia font-size-arvoja — periytyy kortti/listarakenteesta.
   ============================================================ */
.tunneprofiili h3 {
  margin-top: 0;
  margin-bottom: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.tunneprofiili-lista {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
/* Pico-luodin vaimennus <li>-tasolla (vahti: --group=frontend). */
.tunneprofiili-lista li { list-style: none; }
.tunneprofiili-rivi {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
}
.tunneprofiili-rivi:last-child {
  border-bottom: none;
}
.tunneprofiili-nimi {
  color: var(--color-text);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tunneprofiili-arvo {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  white-space: nowrap;
}
/* MOBIILI (0–767 px): vertailuluvullinen arvolohko rivittyy (#1400, palautteet
   #404/#405). Tilaluokka `.vertaileva` — sama kuvio kuin `.kehystysroolit-rivi
   .kaksirivinen`illa — koska SAANTO SAA KOSKEA VAIN NIITA RIVEJA JOILLA VERTAILU ON.
   MITATTU 360 px:ssa (CDP + Emulation.setDeviceMetricsOverride; resize_window ei aja
   aitoja media queryja): `.tunneprofiili-arvo` on nowrap-lohko joka ei voi kutistua
   min-contentinsa alle, joten kolmas elementti otti lisatilansa suoraan NIMESTA —
   "Vastakkainen nakokulma" mahtui ennen kokonaan (179/179 px) ja typistyi
   vertailuluvun jalkeen (147/179). Sama kynnys osuisi myos suoraan `.tunneprofiili-
   arvo`on kirjoitettuna, mutta silloin se muuttaisi MYOS vertailuttomat rivit:
   mitattu, luvut jakautuivat kahdelle riville 15+ muussa kutsupaikassa. */
.tunneprofiili-arvo.vertaileva {
  flex-wrap: wrap;
  justify-content: flex-end;
}
/* EI `flex-basis: 100%` vertailuluvulle, vaikka se olisi ilmeinen tapa pakottaa
   rivinvaihto: mitattu 360 px:ssa etta se tuottaa TASMALLEEN samat nimileveydet
   (161/187/149 px) mutta rivittaa MYOS ne rivit joilla oli tilaa — "Tausta"
   (nimi 46 px) kasvoi 24 -> 56 px korkeaksi ilman syyta. Pelkka `wrap` siirtaa
   luvun omalle rivilleen vain kun se ei mahdu. */
/* Desktop (768 px ylospain, oletusbreakpoint): entinen yhden rivin kaytos. */
@media (min-width: 768px) {
  .tunneprofiili-arvo.vertaileva { flex-wrap: nowrap; }
}
.tunneprofiili-luku {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--color-text);
}
.tunneprofiili-osuus {
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
}
/* Kohortin vertailuluku rivin oman osuuden rinnalla (#1400, palautteet #404/#405).
   Ei uusia font-size-arvoja eika uusia varitokeneita: himmennys on sama kuvio kuin
   .otos-varaus-parilla, jotta rivin OMA luku pysyy hierarkiassa ensimmaisena ja
   vertailu luetaan taustaksi. */
.tunneprofiili-vertailu {
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
  opacity: .8;
}

/* ============================================================
   === Otsikko vs. sisältö (task #512) ===
   Media-sivun "Otsikko vs. sisältö" -palikka: factualAlignment
   misleading/contradicted -tapausten lukumäärä + tuoreimmat
   esimerkkilinkit. Domain-komponentti. Ei prosenttirankingia,
   ei palkkeja (pieni N → vertailu harhaanjohtava). Ei uusia
   font-size-arvoja — periytyy kortti/listarakenteesta.
   ============================================================ */
/* Lohkon OMA otsikko: <h3> + vihje-nappi samalle riville pystykeskitettyna.
   RAJATTU `> header`iin (#882, palaute #178). Rajaamattomana `.otsikko-sisalto h3`
   on jalkelaisselektori, ja #621:n jalkeen lohko sisaltaa artikkelilistan samassa
   <section>issa -> saanto osui MYOS jokaisen artikkelikortin <h3>:een. Silloin
   kortin otsikosta tuli flex-rivi, ja `.otsikko-prefix` ("Analyysi artikkelista:",
   display: block) asettui otsikon VIEREEN omaksi palstakseen sen sijaan etta olisi
   omalla rivillaan kuten kaikissa muissa artikkelilistoissa. <header> on lohkon
   suora lapsi (otsikko-sisalto-kortti.php), artikkelikortit eivat ole sen sisalla. */
.otsikko-sisalto > header h3 {
  margin-top: 0;
  margin-bottom: var(--space-2);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.otsikko-sisalto-luku {
  color: var(--color-muted);
  margin-bottom: var(--space-3);
}
.otsikko-sisalto-lista {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* Pico-luodin vaimennus <li>-tasolla (vahti: --group=frontend). */
.otsikko-sisalto-lista li { list-style: none; }
.otsikko-sisalto-rivi {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--color-border);
}
.otsikko-sisalto-rivi:last-child {
  border-bottom: none;
}
.otsikko-sisalto-meta {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  color: var(--color-muted);
}
.otsikko-sisalto-tila {
  color: var(--color-bias-high-text);
  font-weight: 600;
}
.otsikko-sisalto-pvm {
  font-variant-numeric: tabular-nums;
}

/* --- pilkan-kohteet (#1469) ---
   Mediasivun "Pilkan kohteet": kohde + juttumäärä + 1–2 sitaattia otsikko-
   linkkeineen. Sitaatin tyyli periytyy .mockery-sitaatista (article-mockery).
   Otsikkosääntö rajattu `> header`iin (#882). Pitkät sitaatit ja nimet
   rivittyvät 360 px:ssä (overflow-wrap, frontend-css.md § Vaakaylivuoto). */
.pilkan-kohteet > header h3 {
  margin-top: 0;
  margin-bottom: var(--space-2);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.pilkan-kohteet-lista {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* Pico-luodin vaimennus <li>-tasolla (vahti: --group=frontend). */
.pilkan-kohteet-lista li { list-style: none; }
.pilkan-kohteet-rivi {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  overflow-wrap: anywhere;
}
.pilkan-kohteet-otsake {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0;
}
.pilkan-kohteet-esimerkki {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* ============================================================
   8. KOLMANNEN OSAPUOLEN YLIKIRJOITUKSET — aivan lopuksi
   Lisäosien (payment-widget, captcha, embed, yms.) tyylit
   ylikirjoitetaan täällä. Niputetaan per lisäosa omaan
   lohkoon. Ei sekoiteta omiin komponentteihin.
   ============================================================ */

/* --- bottisuoja (Cloudflare Turnstile, #724) --- */
/* Widget on kiintealeveyksinen iframe (~300x65). Komponentti ei paata
   asemointiaan (spec/frontend-templatointi.md § 2) — .keskita on kutsujan
   antama variantti heroon, oletus seuraa lomakkeen tekstivirtaa. */
.bottisuoja {
  margin-top: var(--space-3);
}
.bottisuoja.keskita {
  display: flex;
  justify-content: center;
}
/* Kelluva variantti (#751): yksi widget koko sivulle, renderoity layoutin
   loppuun. Widget on `interaction-only` eli nakymaton kunnes Turnstile vaatii
   vuorovaikutusta — staattisena layoutin lopussa haaste jaisi sivun alalaitaan
   nakymattomiin ja lahetys nayttaisi jumittuvan syytta. Siksi se kiinnitetaan
   nakymaan. Vasen alalaita: .analyysijono kelluu oikeassa (z-index 50),
   eivat peita toisiaan. z-index 110 = sama kerros kuin ylapalkin dropdownilla
   (ylapalkin 100 ylle, modaalin 200 alle) — haaste on klikattava mutta ei
   ilmesty avatun modaalin paalle.
   Bottom-lauseke on sama kuin .analyysijonolla: Android Chrome palauttaa
   env(safe-area-inset-bottom)=0 vaikka selaimen alapalkki peittaa nakymaa
   (palaute #47), joten minimi on var(--space-5).
   pointer-events: none wrapperissa — nakymaton widget ei saa siepata
   klikkauksia sivun alalaidasta. Haaste-iframe palauttaa auto:n. */
.bottisuoja.kelluva {
  position: fixed;
  left: var(--space-2);
  bottom: max(env(safe-area-inset-bottom, 0px) + var(--space-4), var(--space-5));
  z-index: 110;
  margin-top: 0;
  pointer-events: none;
}
.bottisuoja.kelluva iframe {
  pointer-events: auto;
}
@media (min-width: 481px) {
  .bottisuoja.kelluva {
    left: var(--space-4);
    bottom: var(--space-4);
  }
}

/* --- stripe-payment-element --- */
/* .StripeElement { ... } */

/* --- trustpilot-widget --- */
/* .trustpilot-widget { ... } */
