/*
 * Chat Widget CSS — vapaan chatin widget (chat-widget.js)
 *
 * MIKSI TAMA TIEDOSTO ON OLEMASSA (K1-css-2, auditointi 3.9.2026):
 * Widgetin tyylit asuivat aiemmin projektitiedostossa app/public/css/base.css
 * kolmena epajatkuvana saarekkeena (r362-395, r904-1513, r2131-2246). Silloin
 * widgetilla ei ollut levitettavaa artefaktia: se ei ollut MANIFEST.jsonissa,
 * driftia ei havainnut mikaan, ja jokainen istutus kopioi CSS:nsa siita demosta
 * joka sattui olemaan lahimpana. Kaksi asiakasnakyvaa bradivuotoa (digitaali.fi
 * 11.6.2026, Farizon 19.8.2026) syntyi tasta rakenteesta, ei huolimattomuudesta.
 *
 * Sisarwidget guided-chat-widget.css on ollut alusta asti oma tiedostonsa ja on
 * pysynyt puhtaana. Ero ei ole osaamisessa vaan siina etta tiedosto on se yksikko
 * jonka MANIFEST voi hashata ja jonka istuttaja voi kopioida ilman paatoksia.
 *
 * PREFIKSI: kaikki luokat .cw-*. Widget EI enaa toggla prefiksoimatonta
 * .hidden-luokkaa (K2-css-3) vaan omaa .cw-hidden -luokkaansa, joka maaritellaan
 * tassa tiedostossa. Aiemmin .hidden-maaritys tuli isannalta — .hidden on
 * Bootstrapin, Tailwindin ja lahes jokaisen WP-teeman nimiavaruudessa, joten
 * sopimus oli suora tormaysriski vieraalla sivustolla. Sama koskee .bubble*:ia,
 * josta on nyt .cw-bubble*.
 *
 * `inert`-attribuutti hoitaa saavutettavuuden fokusjarjestyksessa JS:ssa
 * (chat-widget.js setInert()) eika riipu luokan nimesta — se perustelu sailyy
 * koskemattomana.
 *
 * CSP-yhteensopiva: ei inline-styleja, ei ulkoisia fontteja.
 * Ladataan reitilla /chat-core/assets/chat-widget.css (index.php).
 *
 * BRANDAYS: kaikki bradirelevantit arvot ovat design tokeneina .cw-wrapper
 * -scopessa (EI :root, jotta isantasivun globaali nimiavaruus ei saastu).
 *
 * ISTUTUSKOUKUT (aukko A399, 23.9.2026): jokainen token `--cw-X` lukee ensin
 * koukun `--dg-chat-X` ja kayttaa omaa oletustaan vain jos koukkua ei ole:
 *     --cw-primary: var(--dg-chat-primary, #2563eb);
 * Istutus asettaa koukun missa tahansa esi-isassa, myos `:root`issa:
 *     :root { --dg-chat-primary: #c00; }
 * MIKSI KOUKKU EIKA PELKKA `:root { --cw-primary }`: custom property periytyy,
 * mutta ELEMENTIN OMA arvo voittaa perityn aina. Kun core maarittelee tokenin
 * .cw-wrapperissa, istutuksen :root-arvo ei vaikuta lainkaan — hiljaa. Koukku
 * pitaa coren perustyylit teeman sotkemattomina (teeman `--cw-*` :rootissa ei
 * vuoda sisaan) mutta tekee tarkoituksellisesta yliajosta yhden rivin.
 * `:where(.cw-wrapper)` hylattiin: nollaspesifisyys antaisi MINKA TAHANSA
 * teeman saannon voittaa perustyylit. Vanha tapa (.cw-wrapper { --cw-X })
 * toimii edelleen, koska teema latautuu coren jalkeen.
 * Ainoa poikkeus: --cw-panel-dock-overlap (A47:n mittausmekanismi, ei
 * design-arvo). Vahti: app/test/test-widget-css-hooks.js.
 *
 * TYPOGRAFIA: widget EI maarittele font-familya vaan perii isantasivun fontin.
 * Tama on tietoinen valinta: chat naytaa osalta sivustoa eika upotetulta
 * kolmannen osapuolen elementilta. Fonttikoot ovat rem-pohjaisia.
 */

/* ===========================================================
 * DESIGN TOKENS — brandays projektikohtaisesti .cw-wrapperilla
 * Oletusarvot ovat neutraalit; istutus vaihtaa varit brandiin
 * ENNEN kuin widget kytketaan nakyviin (ks. docs/INTEGRATION.md § brandays).
 * =========================================================== */
.cw-wrapper {
    /* --- Varit --- */
    --cw-primary: var(--dg-chat-primary, #2563eb);
    --cw-primary-hover: var(--dg-chat-primary-hover, #1d4ed8);
    --cw-on-primary: var(--dg-chat-on-primary, #ffffff);
    --cw-text: var(--dg-chat-text, #1e293b);
    --cw-text-muted: var(--dg-chat-text-muted, #64748b);
    --cw-bg: var(--dg-chat-bg, #f8fafc);
    --cw-surface: var(--dg-chat-surface, #ffffff);
    --cw-border: var(--dg-chat-border, #e2e8f0);
    --cw-success: var(--dg-chat-success, #16a34a);
    --cw-success-bg: var(--dg-chat-success-bg, #f0fdf4);
    --cw-success-border: var(--dg-chat-success-border, var(--cw-border));  /* lead-lomakkeen reuna; oletus neutraali */
    --cw-success-text: var(--dg-chat-success-text, var(--cw-success));   /* onnistumisviesti; istuttaja voi tummentaa */
    --cw-error: var(--dg-chat-error, #dc2626);
    --cw-warning: var(--dg-chat-warning, #d97706);  /* kuvatulosten varoitukset (.cw-image-*, A138) */

    /* Chat-kuplat */
    --cw-bubble-user: var(--dg-chat-bubble-user, var(--cw-primary));
    --cw-bubble-user-text: var(--dg-chat-bubble-user-text, var(--cw-on-primary));
    --cw-bubble-assistant: var(--dg-chat-bubble-assistant, var(--cw-surface));
    --cw-bubble-assistant-text: var(--dg-chat-bubble-assistant-text, var(--cw-text));
    /* Kuplan riviväli on TOKENI eikä literaali, koska sitä luetaan kahdesta
     * paikasta: `.cw-bubble` (kuplan oma tekstiasu) ja kollapsoinnin
     * `max-height`-laskenta (aukko A56). Kaksi literaalia 1.55:tä ajautuisivat
     * erilleen ensimmäisessä säädössä, ja seuraus olisi hiljainen: kollapsoitu
     * kupla näyttäisi puolikkaan rivin tai jättäisi tyhjää klippauksen alle. */
    --cw-bubble-line-height: var(--dg-chat-bubble-line-height, 1.55);
    /* Montako riviä kayttajan omasta pitkasta viestista nakyy kutistettuna
     * (aukko A56). Perustelu oletukselle 2: ks. kollapsointilohko alempana. */
    --cw-bubble-collapse-lines: var(--dg-chat-bubble-collapse-lines, 2);
    /* A706 (Teemu 1.10.2026, DEMO: "en mä näe tosta mittään"): mobiilin
     * leipateksti. Tyopoydan kupla on 0.8rem (12.8 px 16 px juurella) — kontrasti
     * on kunnossa (#1e293b/#fff 14.6:1), mutta koko on puhelimessa liian pieni.
     * 0.9375rem = 15 px. Kaytetaan vain MOBIILI-lohkossa (<576 px). */
    --cw-bubble-font-size-mobile: var(--dg-chat-bubble-font-size-mobile, 0.9375rem);
    /* Syotekentta mobiilissa 1rem = 16 px: iOS Safari ZOOMAA sivua kun alle
     * 16 px:n kenttaan fokusoidaan (0.85rem = 13.6 px) — ruutu hyppaa (vrt.
     * A703). Istutus voi pienentaa, mutta zoomaus palaa alle 16 px:n. */
    --cw-input-font-size-mobile: var(--dg-chat-input-font-size-mobile, 1rem);

    /* KUPLAN SISALLON TYPOGRAFIA JA VALISTYS (K2-css-38, 23.9.2026)
     *
     * Kuplan <p>/lista/otsikkosaannot ja widgetin kappaleiden kuristuspiste
     * kantavat `!important`ia (aukot A380/A381: isannan
     * `p { margin-bottom: 25px !important }` voittaa muuten). Arvot ovat
     * silti TOKENEITA eivatka literaaleja: `!important` koskee DEKLARAATIOTA,
     * ei arvoa — `margin: 0 0 var(--cw-bubble-p-gap) !important` voittaa
     * isannan ja lukee arvonsa tavallisesta kaskadista. Istutuksen theme.css
     * saataa siis kappalevalia yliajamalla tokenin `.cw-wrapper`-lohkossa
     * (sama mekanismi kuin --cw-primary) ilman omaa `!important`-spesifisyys-
     * sotaa. `em`-arvot resolvoituvat KAYTTOKOHDASSA (rekisteroimaton custom
     * property on tokenivirta), eli ne suhteutuvat kuplan/otsikon omaan
     * fonttikokoon kuten ennenkin.
     *
     * 🔴 --cw-bubble-p-gap syottaa KOLME saantoa: kuplan <p>, kuplan listojen
     * pystymarginaalin (A172: listan valin on TARKOITUKSELLA oltava sama kuin
     * kappaleen) ja widgetin `.cw-wrapper p` -kuristuspisteen (A381). Yksi
     * token pitaa ne yhdessa — kaksi literaalia ajautuisi erilleen. */
    --cw-bubble-p-gap: var(--dg-chat-bubble-p-gap, 1em);
    --cw-bubble-list-indent: var(--dg-chat-bubble-list-indent, 1.4em);
    --cw-bubble-li-gap: var(--dg-chat-bubble-li-gap, 0.15em);
    --cw-bubble-heading-line-height: var(--dg-chat-bubble-heading-line-height, 1.3);
    --cw-bubble-heading-weight: var(--dg-chat-bubble-heading-weight, 600);
    --cw-bubble-heading-margin: var(--dg-chat-bubble-heading-margin, 0.6em 0 0.3em);
    --cw-bubble-h1-size: var(--dg-chat-bubble-h1-size, 1.25em);
    --cw-bubble-h2-size: var(--dg-chat-bubble-h2-size, 1.15em);
    --cw-bubble-h3-size: var(--dg-chat-bubble-h3-size, 1.05em);
    /* Liidilomakkeen perustelurivi (.cw-lead-reason, <p>, A381) */
    --cw-lead-reason-size: var(--dg-chat-lead-reason-size, 0.85rem);
    --cw-lead-reason-gap: var(--dg-chat-lead-reason-gap, 10px);

    /* Taustan himmennys kun paneeli auki (ei kovakoodattua rgba:ta saannoissa) */
    --cw-overlay: var(--dg-chat-overlay, rgba(0, 0, 0, 0.12));

    /* Overlay-PINTA (task 393, .cw-overlay) — lomake/kortit chat-ruudun paalla.
     * 🔴 Eri asia kuin --cw-overlay ylla: se on paneelin TAUSTAN himmennys
     * (.cw-wrapper.cw-active::before), nama ovat paneelin paalle avautuvan
     * pinnan. Nimet jakavat etuliitteen koska `.cw-overlay` on luokan nimi.
     * Oletukset viittaavat olemassa oleviin tokeneihin -> teeman brandays
     * periytyy ilman erillista yliajoa. */
    --cw-overlay-bg: var(--dg-chat-overlay-bg, var(--cw-surface));
    --cw-overlay-border: var(--dg-chat-overlay-border, var(--cw-border));
    --cw-overlay-shadow: var(--dg-chat-overlay-shadow, none);
    --cw-overlay-radius: var(--dg-chat-overlay-radius, var(--cw-radius-panel));
    /* Paneelin korkeus overlayn ollessa auki (dokissa; upotettuna sailion).
     * Oletus = paneelin maksimi, eli overlay EI muuta dockin ulkomittoja.
     * Teema voi nostaa (pitka lomake); enintaan 85vh. Sisalto vierii. */
    --cw-overlay-max-height: var(--dg-chat-overlay-max-height, var(--cw-panel-max-height));
    /* Rullaverho (task 418, 8c): overlay nousee paneelin alareunasta ja
     * laskeutuu sulkiessa. transform only (ei layoutia liikuttavia ominaisuuksia).
     * JS lukee keston varmistusajastimeen (overlayDurationMs). */
    --cw-overlay-duration: var(--dg-chat-overlay-duration, 250ms);
    --cw-overlay-easing: var(--dg-chat-overlay-easing, cubic-bezier(0.2, 0.8, 0.2, 1));
    /* Tiivis overlay tyopoydalla >= 768 px (task 418, kohta 7): liidilomake
     * mahtuu paneeliin ilman vieritysta. Mobiili kayttaa perussaantoja. */
    --cw-overlay-header-padding: var(--dg-chat-overlay-header-padding, 0.25rem 0.5rem 0.25rem 1rem);
    --cw-overlay-body-padding: var(--dg-chat-overlay-body-padding, 0.6rem 1rem 0.75rem);
    --cw-lead-label-width: var(--dg-chat-lead-label-width, 7rem);
    --cw-lead-gap: var(--dg-chat-lead-gap, 0.4rem 0.75rem);
    --cw-lead-input-padding: var(--dg-chat-lead-input-padding, 0.35rem 0.5rem);
    --cw-lead-input-line-height: var(--dg-chat-lead-input-line-height, 1.25);
    --cw-overlay-close-size: var(--dg-chat-overlay-close-size, 2.25rem);
    --cw-lead-check-size: var(--dg-chat-lead-check-size, 1rem);
    /* Eleen nousu nostohetkella (A448, kumoaa task 418:n 6d-renkaan): YKSI
     * kerta, ei sykkiva. Tokenin nimi sailyy (--cw-ele-flash-*), jotta
     * istutuksen yliajo ei katoa hiljaa; luokka on yha .cw-ele--flash. */
    --cw-ele-flash-duration: var(--dg-chat-ele-flash-duration, 450ms);

    /* --- IKONINAPIT JA ISTUTUSKOUKUT (task 419, aukko A436) ---
     *
     * Otsikkopalkin kolme nappia (☰ + ✕), overlayn ja eleen sulkunappi olivat
     * fonttiglyfeja: eri levyisia (mitattu PEL 22.9.: 27 / 22 / 29 px), ☰ istui
     * eri korkeudella kuin otsikko, ja + nakyi ✕:aa pienempana. Nyt kaikki ovat
     * SAMAA 24px-SVG-sarjaa (Material, viiva 2/24) maskina `::before`issa:
     * vari tulee `currentColor`ista, joten istutuksen tekstivari ohjaa ikonia.
     *
     * 🔑 SVG ON INLINE-DATAA EIKA TIEDOSTOPOLKU. Maskikuvan lataus on hiljainen:
     * jos tiedostoa ei saada (polku, oikeudet, basic auth), nappi jaa tyhjaksi
     * ruuduksi eika mikaan virheile (mitattu PEL 22.9.: `close-24px.svg`-polulla
     * ruksi ei renderoitynyt lainkaan). Coren CSP sallii `img-src data:`
     * (security-headers.php); isantasivun oma CSP on isannan asia.
     * Istutus voi vaihtaa ikonin yliajamalla tokenin `url(...)`-arvolla. */
    --cw-icon-close: var(--dg-chat-icon-close, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E"));
    --cw-icon-plus: var(--dg-chat-icon-plus, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6z'/%3E%3C/svg%3E"));
    --cw-icon-menu: var(--dg-chat-icon-menu, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 18h18v-2H3zm0-5h18v-2H3zm0-7v2h18V6z'/%3E%3C/svg%3E"));
    /* Otsikkopalkin napit samaan kiinteaan ruutuun (28 px). Plus ja valikko
     * 20 px, ruksi 18 px: viisto ruksi nayttaa samankokoisena isommalta. */
    --cw-header-btn-size: var(--dg-chat-header-btn-size, 1.75rem);
    --cw-header-icon-size: var(--dg-chat-header-icon-size, 1.25rem);
    --cw-header-close-icon-size: var(--dg-chat-header-close-icon-size, 1.125rem);
    /* Symmetrinen pystypehmuste (A4): entinen `0.3rem 0.5rem 0` jatti
     * alareunan nollaan, ja ikonirivi istui palkissa ylhaalla. */
    --cw-panel-header-padding: var(--dg-chat-panel-header-padding, 0.25rem 0.5rem);
    /* Otsikkopalkin tausta ja vahimmaiskorkeus (A555). Paneelin otsikko JA
     * historian sivupalkin otsikko lukevat samat tokenit, joten istutuksen
     * teema asettaa palkin ilmeen yhdesta kohdasta. Oletukset = entinen tila
     * (ei taustaa, korkeus sisallon mukaan). */
    --cw-panel-header-bg: var(--dg-chat-panel-header-bg, transparent);
    --cw-panel-header-min-height: var(--dg-chat-panel-header-min-height, auto);
    /* Historialistan rivit (A556). Oletukset = entiset literaalit: rivit
     * reunasta reunaan, ei valia eika pyoristysta, erotin alareunassa. */
    --cw-conversation-list-padding: var(--dg-chat-conversation-list-padding, 0.25rem 0);
    --cw-conversation-gap: var(--dg-chat-conversation-gap, 0);
    --cw-conversation-padding: var(--dg-chat-conversation-padding, 0.5rem 0.75rem);
    --cw-conversation-radius: var(--dg-chat-conversation-radius, 0);
    --cw-conversation-border: var(--dg-chat-conversation-border, 1px solid var(--cw-border));
    --cw-overlay-close-icon-size: var(--dg-chat-overlay-close-icon-size, 1.125rem);
    /* Overlayn sulkunapin kosketuskohde alle 768 px (WCAG 2.5.5, 44 px).
     * Tyopoydalla --cw-overlay-close-size (36 px > 2.5.8:n 24 px). */
    --cw-overlay-close-size-touch: var(--dg-chat-overlay-close-size-touch, 2.75rem);
    --cw-ele-dismiss-icon-size: var(--dg-chat-ele-dismiss-icon-size, 0.875rem);
    /* Eleen toiminto on toiminto eika leipateksti (A11): 600 erottaa sen
     * saman rivin selitteesta ilman uutta varia. */
    --cw-ele-font-weight: var(--dg-chat-ele-font-weight, 600);

    /* Paneelin reuna (A13). Koukku, jotta istutus voi nollata reunan
     * (`--cw-panel-border: 0`) ilman spesifisyyskilpailua upotetun moodin
     * (0,3,0)-saannon kanssa. Koko shorthand, ei pelkka vari. */
    --cw-panel-border: var(--dg-chat-panel-border, 1px solid var(--cw-border));

    /* Viestialueen ja kuplan valit (A14). Oletukset = entiset literaalit. */
    --cw-messages-padding: var(--dg-chat-messages-padding, 0.3rem 0.75rem);
    --cw-messages-gap: var(--dg-chat-messages-gap, 0.4rem);
    --cw-bubble-padding: var(--dg-chat-bubble-padding, 0.4rem 0.7rem);
    --cw-bubble-avatar-gap: var(--dg-chat-bubble-avatar-gap, 0.45rem);

    /* Liidilomakkeen kentat (A8). Entinen reuna --cw-border (#e2e8f0) on
     * 1,23:1 valkoista vasten -> kentta ei erotu (WCAG 1.4.11 vaatii 3:1
     * kayttoliittymakomponentin rajalle). #848fa0 = 3,27:1 valkoista
     * (--cw-surface, overlay) ja 3,13:1 --cw-bg:ta (#f8fafc) vasten.
     * 🔴 EI muuteta globaalia --cw-borderia: se piirtaa myos koriste-
     * erottimia (paneeli, kapseli, listarivit), joille 3:1 ei ole vaatimus. */
    --cw-input-bg: var(--dg-chat-input-bg, var(--cw-surface));
    --cw-input-border: var(--dg-chat-input-border, #848fa0);
    --cw-input-radius: var(--dg-chat-input-radius, var(--cw-radius));

    /* Syoterivin ohjainkoko (A17): lahetysnappi JA kapselin kasvokuva.
     * Kun kuva oli 2.25rem ja nappi 2rem, kuva maarasi kapselin korkeuden
     * ja kentta hyppasi kun kuva piilotettiin chatin auetessa (PEL: 50,5 ->
     * 46 px). Yhteinen token pitaa sisaltokorkeuden samana molemmissa. */
    --cw-input-control-size: var(--dg-chat-input-control-size, 2rem);
    --cw-input-avatar-size: var(--dg-chat-input-avatar-size, var(--cw-input-control-size));
    /* Lahetysnapin tausta (A399). Oma token, jotta istutus voi erottaa napin
     * brandivarista koskematta kupliin ja otsikkoon. Oletus = --cw-primary,
     * eli ulkoasu ei muutu. Lukijat: .cw-send-btn + cw-arrive-flash. */
    --cw-send-bg: var(--dg-chat-send-bg, var(--cw-primary));

    /* Nayttamohahmo (A5, >= 576 px). Leveys on myos viestialueen varaus. */
    --cw-stage-avatar-width: var(--dg-chat-stage-avatar-width, 8rem);
    --cw-stage-avatar-max-height: var(--dg-chat-stage-avatar-max-height, 12rem);

    /* --- Mitat --- */
    --cw-radius: var(--dg-chat-radius, 0.5rem);
    --cw-radius-lg: var(--dg-chat-radius-lg, 0.75rem);
    --cw-radius-panel: var(--dg-chat-radius-panel, 1.25rem);
    --cw-radius-capsule: var(--dg-chat-radius-capsule, 2rem);
    --cw-panel-max-width: var(--dg-chat-panel-max-width, 32rem);
    /* A695 (Teemu 1.10.2026): korkeampi ikkuna, mutta EI KOSKAAN yli
     * viewportin (puhelimen vaakatasossa kiintea 24rem vei ylareunan ruudun
     * yli). --cw-dock-vh = window.innerHeight ja --cw-dock-band = syoterivin
     * ja alareunan vali, molemmat dockin JS:n mittaamia (dockMeasure).
     * innerHeight on DYNAAMINEN viewport (= 100dvh, selainpalkit huomioiden)
     * ja toimii myos vanhoissa selaimissa; 100vh on fallback ennen mittausta.
     * 🔴 EI `100dvh`-riviä @supports-lohkoon: tokenin uudelleenmaarittely
     * lohkon ulkopuolella ohittaisi koukun (A399, test-widget-css-hooks.js H3). */
    --cw-panel-max-height: var(--dg-chat-panel-max-height, min(32rem, calc(var(--cw-dock-vh, 100vh) - var(--cw-dock-band, 0px) - 0.75rem)));
    --cw-content-max-width: var(--dg-chat-content-max-width, 36rem);

    /* PANEELIN LIMITYS SYOTERIVIN TAAKSE (aukko A47, 18.9.2026)
     *
     * OIRE (Teemu 17.9. klo 00:29): "kun kirjoitan useamman rivin tekstia input
     * laatikko paisuu, ja se tyontaa koko chat palikkaa ylospain paljastaen
     * taustakuvan rajat. Se inputtiruutu vois kasvaa ylospain chat lohkon
     * sisalla niin, etta chat lohko pysyy paikallaan ja inputti kasvaa vaan
     * sisaanpain." Sama periaate kuin A43:ssa: IKKUNAN SISALTO MUKAUTUU,
     * ULKOMITAT EIVAT.
     *
     * MEKANISMI: `.cw-wrapper` on flex-sarake ilman omaa korkeutta, joten sen
     * korkeus = paneelin margin-box + syoterivi = P - limitys + R. Kun limitys
     * oli VAKIO 4.5rem ja R kasvaa autoResizen mukana (rows 1->4, mitattu
     * ~3.2rem -> ~6.75rem), erotus vuoti suoraan wrapperin YLAREUNAAN. Kun
     * limitys = R, korkeus on P riippumatta syoterivista — eli kasvu syo
     * viestialuetta, ei tyonna lohkoa.
     *
     * 🔴 MIKSI `max(..., 4.5rem)` EIKA PALJAS `var(--cw-dock-height, 4.5rem)`:
     * kaksi eri syyta, molemmat mitattuja.
     *   1. `--cw-dock-height` on `0px` ennen ensimmaista mittausta
     *      (dockPublishIdle() ajetaan moduulitasolla) ja silloin kun syoterivin
     *      korkeus on 0 (istutus piilottanut widgetin). var()-fallback EI
     *      laukea nollalla — se laukeaa vain kun muuttujaa ei ole lainkaan.
     *      Paljas var() tuottaisi siis limityksen 0 eli rikkinaisen lepotilan.
     *   2. Lattia tekee muutoksesta regressiovapaan: lepotilassa mitattu rivi
     *      (~3.2rem) on ALLE lattian, joten limitys pysyy 4.5rem:ssa eli tasan
     *      nykyisessa arvossaan. Kaytos POIKKEAA nykyisesta vain silloin kun
     *      rivi on KORKEAMPI kuin 4.5rem — eli tasan siina tilanteessa joka on
     *      tama vika. Mikaan muu tila ei muutu.
     *
     * LAHDE: `--cw-dock-height` = syoterivin mitattu korkeus px:na
     * (chat-widget.js:907), jonka ResizeObserver paivittaa rAF-throttlattuna
     * kun `rows` kasvaa (chat-widget.js:1016 observoi els.inputRow). Muuttuja
     * on julkaistu isantateemoille INTEGRATION.md:ssa; tassa se on KULUTTAJA
     * eika toinen totuus. JS kirjoittaa sen `document.documentElement`in
     * inline-styleen, joten wrapper perii sen eika isanta voi ohittaa sita
     * vahingossa. */
    --cw-panel-dock-overlap: max(var(--cw-dock-height, 4.5rem), 4.5rem);

    /* --- Fokus (saavutettavuus) ---
     * 🔴 KONTRASTIVAATIMUS: WCAG 2.2 / 1.4.11 (Non-text Contrast) vaatii
     * fokusindikaattorilta VAHINTAAN 3:1 kontrastin viereiseen pintaan.
     * Jos istutus yliajaa taman tokenin, se on MITATTAVA — brandivari ei ole
     * automaattisesti kelpo fokusvari.
     *
     * 🔴 MITTAA TOKENI, ALA VARIA. Tama virhe on tehty jo kahdesti (RC ja
     * Farizon; loydos K1-css-24, 22.9.2026), ja se tehtiin nojaamalla TAHAN
     * kommenttiin — siksi se on korjattu tassa. Lapinakyva rengas EI ole se
     * vari jonka silma nakee: selain komposoi sen takanaan olevaan pintaan.
     * Farizonin #333D4F on LAPINAKYMATTOMANA 10,93 eli reilusti lapi, mutta
     * TOIMITETTUNA `rgba(51, 61, 79, 0.45)` se on 2,40 valkoista pintaa ja
     * 2,27 kapselin reunaa vasten eli HYLATTY. Alfa soi kontrastin.
     * (Heidan brandioranssinsa #FF6432 on lapinakymattomanakin vain 2,95 eli
     * hylatty jo ennen alfaa — molemmat ovat hylkyja, mutta eri syysta.)
     *
     * 🔴 NAIN VAADITTU ALFA LASKETAAN — sita ei nae silmalla: komposoi vari
     * jokaiseen pintaan kaavalla `c = a*fg + (1-a)*bg` per kanava, laske
     * WCAG-relatiiviluminanssi ja kontrastisuhde, ja ota tulokseksi HUONOIN
     * pinta (se on yleensa --cw-border, ei valkoinen). Nosta alfaa kunnes
     * huonoin tayttaa vaatimuksen. Tata ei tarvitse tehda kasin:
     * `app/test/test-widget-focus-css.js` (vaite D5) laskee sen lahteesta ja
     * TULOSTAA pienimman lapaisevan alfan aina kun vaite ei mene lapi.
     * Per-istutus-lukuja ei kirjata tahan: ne ovat kerros 3:a, ja niiden
     * paikka on istutuksen omassa `theme.css`:ssa tokenin vieressa.
     *
     * OLETUSARVO (aukko A33, Teemun paatos 19.9.2026, commit 43a16c2:
     * "alpha 0.4 -> kunnollinen kontrasti + --cw-focus-ring ylikirjoitettavissa
     * WP-teemasta"). Vanha `rgba(37, 99, 235, 0.4)` oli 1,80 / 1,78 / 1,71:1
     * (surface / bg / border) eli hylky; syy oli alfa, ei vari. Uusi oletus on
     * sama #2563eb LAPINAKYMATTOMANA: 5,17:1 --cw-surface, 4,94:1 --cw-bg ja
     * 4,19:1 --cw-border vasten (laskettu D5:lla 23.9.2026).
     *
     * 🔑 MIKSI KIINTEA VARI EIKA var(--cw-primary): istutus vaihtaa
     * --cw-primaryn brandiin, ja vaalea brandivari pudottaisi renkaan alle
     * rajan HILJAA (Farizonin #FF6432 on lapinakymattomanakin 2,95:1). Rengas
     * seuraa brandia vain kun istutus sen itse valitsee ja mittaa.
     *
     * YLIKIRJOITUS (kolme koukkua, ks. INTEGRATION.md § Brandays):
     *   --dg-chat-focus-ring-color  pelkka vari (tavallisin)
     *   --dg-chat-focus-ring-width  renkaan paksuus
     *   --dg-chat-focus-ring        koko box-shadow-arvo (vari + leveys ohi)
     * Istutusten mittaus: `node app/tools/fokuskontrasti-istutukset.js`.
     *
     * MIKSI BOX-SHADOW EIKA OUTLINE/BORDER-COLOR: rengas piirretaan
     * box-shadowna, joka seuraa elementin `border-radius`ia automaattisesti.
     * Siksi token on SHADOW-ARVO, ei vari. `border-color: var(--cw-focus-ring)`
     * on siis virheellista CSS:aa ja tuottaa HILJAISEN vaaran tuloksen
     * (invalid-at-computed-value-time -> currentColor), ei virheilmoitusta. */
    --cw-focus-ring-color: var(--dg-chat-focus-ring-color, #2563eb);
    --cw-focus-ring-width: var(--dg-chat-focus-ring-width, 3px);
    --cw-focus-ring: var(--dg-chat-focus-ring, 0 0 0 var(--cw-focus-ring-width) var(--cw-focus-ring-color));

    /* MOBIILIN SYOTEKAPSELIN FOKUSREUNA (< 576 px). Teemu 1.10.2026 demossa:
     * "Mobiilissa sininen 'focus' input kentan ymparilla on aivan liikaa, ota
     * sininen vari pois mobiililla, tummenna vaa input kentan reunaa vahan".
     * Mobiilissa kapselin fokusindikaattori on REUNAVARI, ei --cw-focus-ring
     * (saanto MOBIILI-lohkossa). Tyopoyta pysyy renkaassa.
     *
     * 🔴 INDIKAATTORI EI SAA KADOTA (WCAG 2.4.7): tama vari on mobiilin AINOA
     * fokusmerkki. Oletus #848fa0 = 3,27:1 valkoista (--cw-surface) vasten
     * (WCAG 1.4.11, laskettu test-widget-focus-css.js M7:lla). Mittaa jos vaihdat.
     *
     * MIKSI KIINTEA ARVO EIKA var(--cw-input-border) (sama heksa): istutus saa
     * asettaa lomakekentan reunan `transparent` (INTEGRATION.md, reunaton
     * taytto), ja ketjutettuna mobiilin fokus katoaisi silloin hiljaa.
     * Ylikirjoitus: --dg-chat-input-border-focus-mobile. */
    --cw-input-border-focus-mobile: var(--dg-chat-input-border-focus-mobile, #848fa0);

    /* --- Varjot --- */
    --cw-shadow-xs: var(--dg-chat-shadow-xs, 0 1px 2px rgba(0, 0, 0, 0.06));
    --cw-shadow-sm: var(--dg-chat-shadow-sm, 0 2px 8px rgba(0, 0, 0, 0.06));
    --cw-shadow-panel: var(--dg-chat-shadow-panel, 0 -4px 16px rgba(0, 0, 0, 0.08));

    /* --- Transitiot --- */
    --cw-transition: var(--dg-chat-transition, 0.2s);
    --cw-transition-fast: var(--dg-chat-transition-fast, 0.15s);

    /* --- Latausindikaattori (aukko A45) ---
     * MIKSI TOKENIT: "chatti miettii" -animaatio on se kohta jossa istutus haluaa
     * nayttaa oman hahmonsa — mutta hahmokuva on kerros 3, ei corea. Ilman
     * tokeneita ainoa tapa vaihtaa animaatio olisi ylikirjoittaa coren saannot
     * istutuksen CSS:ssa, ja se on drift jota ei nae kukaan ennen kuin coren
     * saanto muuttuu ja ylikirjoitus jaa puolitiehen.
     *
     * KAKSI INDIKAATTORIA, YHDET TOKENIT: `.cw-typing` (AI) ja
     * `.cw-agent-typing__dots` (myyja/takeover) ovat eri elementteja mutta
     * sama animaatio. Istutus ei saa joutua saatamaan kahta paikkaa, joten
     * PISTETOKENIT (gap, dot-*) luetaan molemmissa.
     * KUPLATOKENIT (bg, padding, radius, shadow) koskevat vain `.cw-typing`ia:
     * se on itsenainen kupla, kun taas myyjan pisteet elavat jo olemassa
     * olevan kuplan sisalla omalla tiiviimmalla paddingillaan. Yhteinen
     * padding muuttaisi myyjan merkin ulkoasua — tama ei ole unohdus.
     *
     * OLETUKSET = nykyinen ulkoasu tavulleen. Kytkematta mitaan widget
     * renderoityy tasan kuten ennen tokeneita.
     * Ks. docs/INTEGRATION.md § Latausindikaattori. */
    --cw-typing-bg: var(--dg-chat-typing-bg, var(--cw-surface));
    --cw-typing-padding: var(--dg-chat-typing-padding, 0.4rem 0.7rem);
    --cw-typing-radius: var(--dg-chat-typing-radius, var(--cw-radius-lg));
    --cw-typing-shadow: var(--dg-chat-typing-shadow, var(--cw-shadow-xs));
    --cw-typing-gap: var(--dg-chat-typing-gap, 0.25rem);
    --cw-typing-dot-size: var(--dg-chat-typing-dot-size, 0.4rem);
    --cw-typing-dot-color: var(--dg-chat-typing-dot-color, var(--cw-text-muted));
    --cw-typing-dot-display: var(--dg-chat-typing-dot-display, block);
    --cw-typing-animation: var(--dg-chat-typing-animation, cw-bounce 1.2s infinite);
    --cw-typing-delay-2: var(--dg-chat-typing-delay-2, 0.15s);
    --cw-typing-delay-3: var(--dg-chat-typing-delay-3, 0.3s);
    /* Hahmokuva pisteiden tilalle: istutus antaa url()-arvon ja vaihtaa
     * molemmat display-tokenit (media block, dot none). Kaksi erillista
     * displayta eika yhta kytkinta, koska CSS:ssa ei ole ehtolausetta —
     * pelkka url() ei voi piilottaa pisteita. */
    --cw-typing-media: var(--dg-chat-typing-media, none);
    --cw-typing-media-display: var(--dg-chat-typing-media-display, none);
    --cw-typing-media-size: var(--dg-chat-typing-media-size, 1.75rem);

    /* --- Tyokalun tilarivi (A45 tool-status-osuus, A372, 23.9.2026) ---
     * Nakyy vastauskuplassa kun malli ajaa tyokalua. Oletus on pieni pyoriva
     * rengas tekstin edessa (ei ⏳-merkkia: emoji renderoityy joka alustalla
     * eri nakoisena eika sita voi tyylitella). SAMA kaava kuin --cw-typing-*:
     * istutus vaihtaa renkaan hahmokuvaan kerros 3:ssa koskematta coreen:
     *   --cw-tool-status-media: url(hahmo.webp);
     *   --cw-tool-status-ring-width: 0;
     *   --cw-tool-status-indicator-radius: 0;
     *   --cw-tool-status-animation: oma-keyframe 1s ease-in-out infinite;
     * Tekstit (ja niiden vaihtuminen) tulevat JS:sta, ks. config['chat.tool_status_texts']
     * ja docs/INTEGRATION.md § Tyokalun tilarivi. */
    --cw-tool-status-gap: var(--dg-chat-tool-status-gap, 0.4rem);
    --cw-tool-status-indicator-display: var(--dg-chat-tool-status-indicator-display, block);
    --cw-tool-status-indicator-size: var(--dg-chat-tool-status-indicator-size, 0.85rem);
    --cw-tool-status-indicator-radius: var(--dg-chat-tool-status-indicator-radius, 50%);
    --cw-tool-status-ring-width: var(--dg-chat-tool-status-ring-width, 2px);
    --cw-tool-status-ring-color: var(--dg-chat-tool-status-ring-color, var(--cw-border));
    --cw-tool-status-ring-accent: var(--dg-chat-tool-status-ring-accent, var(--cw-text-muted));
    --cw-tool-status-media: var(--dg-chat-tool-status-media, none);
    --cw-tool-status-animation: var(--dg-chat-tool-status-animation, cw-spin 0.9s linear infinite);

    /* --- Saapumisanimaatio (task 281) ---
     * Kaksi animaatiota: docki nousee paikalleen kerran istunnossa, ja hetki sen
     * jalkeen lahetysnappi valahtaa kehotuksena. Istutus saataa nama kerros
     * 3:sta koskematta core-tiedostoon (sama periaate kuin --cw-dock-offset).
     * Ajoituksen AINOA totuus on tassa — JS ei lue naita arvoja, vaan seuraa
     * animationend-tapahtumaa. Ks. docs/INTEGRATION.md § Saapumisanimaatio.
     * OPT-OUT: --cw-arrive-duration: 0s. */
    --cw-arrive-delay: var(--dg-chat-arrive-delay, 0.6s);
    --cw-arrive-duration: var(--dg-chat-arrive-duration, 0.42s);
    --cw-arrive-easing: var(--dg-chat-arrive-easing, cubic-bezier(0.33, 0, 0.2, 1));
    /* Aloitusetaisyys lepotilan alapuolella. 🔴 EI pelkka 100%: se on wrapperin
     * OMA korkeus (~58px), joten evastebannerin takia nostettu widget ei
     * poistuisi ruudulta vaan liukuisi bannerin paalle. --cw-dock-bottom on
     * coren julkaisema efektiivinen bottom (aina px), joten matka on oikea
     * myos vaistetyssa tilassa. Fallback 0px on pakollinen: sivulla jolla
     * widget-skriptia ei ladata muuttujaa ei ole, ja maarittelematon custom
     * property tekee calc():sta INVALIDIN, ei nollan. */
    --cw-arrive-travel: var(--dg-chat-arrive-travel, calc(100% + var(--cw-dock-bottom, 0px)));
    --cw-arrive-overshoot: var(--dg-chat-arrive-overshoot, 9px);
    --cw-arrive-shadow: var(--dg-chat-arrive-shadow, 0 8px 28px rgba(0, 0, 0, 0.28));
    --cw-arrive-flash-delay: var(--dg-chat-arrive-flash-delay, 0.28s);
    --cw-arrive-flash-duration: var(--dg-chat-arrive-flash-duration, 0.7s);
    --cw-arrive-flash-count: var(--dg-chat-arrive-flash-count, 2);

    /* --- Layout --- */
    --cw-z: var(--dg-chat-z, 50);
}

/* ===========================================================
 * PIILOTUS
 * Widgetin oma piilotusluokka. EI .hidden — ks. tiedoston alun perustelu.
 * display:none poistaa myos tabulointijarjestyksesta; `inert` JS:ssa on
 * varmistus sen varalta etta istutus animoi taman opacityllä.
 * =========================================================== */
.cw-hidden {
    display: none !important;
}

/* ===========================================================
 * ISANTA-CSS:N ERISTYS — widgetin napit (aukko A446, task 435)
 * Isantasivun globaali lomake-CSS kirjoittaa paljaille `button`- ja
 * `[type=button]`-elementeille marginaalin, varjon ja appearancen. Mitattu
 * (app/test/test-widget-host-css.js, Pico): elepalkin napeille 21 px
 * alamarginaali, kaikille napeille isannan box-shadow (nakyy fokuksessa
 * isannan renkaana widgetin oman sijaan) ja `appearance: button`
 * (iOS Safari piirtaa silla natiivin napin widgetin tyylin alle).
 *
 * Arvot = selaimen oletus jota widget jo nayttaa ilman isantaa, eli ulkoasu
 * ei muutu. Tama lohko on TIEDOSTON ALUSSA tarkoituksella: napin oma saanto
 * myohemmin (esim. overlayn X:n margin-left: auto) voittaa sen
 * samalla spesifisyydella jarjestyksella. UUSI NAPPI -> lisaa se tahan listaan
 * ja valjaaseen (need-lista).
 *
 * 🔴 LATAUSJARJESTYS EI SAA RATKAISTA (aukko A468, task 505). WP:ssa plugari
 * jonottaa tyylinsa ENNEN teemaa, joten isannan `[type=button]` (0,1,0)
 * latautuu widgetin JALKEEN ja voitti tasapelin kaikille yksiluokkaisille
 * nappisaannoille (valjaan H7: 192 eroa Picoa vastaan). Siksi niiden nappien
 * saannot, joilla on `type="button"` (JS asettaa), ovat muotoa
 * `.cw-wrapper .cw-X` = (0,2,0): voittaa isannan `[type=button]`-,
 * `button:hover`- (0,1,1) ja `input:not(...)`- (0,1,1) tason saannot
 * JARJESTYKSESTA RIIPPUMATTA. Koskee luokan KAIKKIA saantoja (perus, tilat,
 * media-yliajot) — muuten luokan oma 0,1,0-saanto haviaisi omalle
 * eristyssaannolleen. `::before`-maskit jaavat yksiluokkaisiksi: isanta ei
 * tyyliteele nappien pseudoelementteja.
 *   A607 (task 537): historia, sulje, sivupalkin X ja laheta saivat
 *   `type="button"`in (isannan <form>-kaareessa oletus-submit lahettaisi
 *   isannan lomakkeen), joten ne osuvat nyt `[type=button]`iin ja nostettiin
 *   samaan muotoon. Liidin laheta (`.cw-lead-form button` 0,1,1) on jo
 *   type="button"; valjas mittaa kaikki.
 *
 * ⚠️ MIKSI EI `@layer`: kerroksettomat saannot voittavat kerrostetut, joten
 * widget layerissa haviaisi JOKAISELLE teeman paljaalle `button`-saannolle —
 * juuri painvastoin kuin tarvitaan. `:where()` nollaisi spesifisyyden, ja
 * `!important` veisi teemalta yliajomahdollisuuden (kuplan typografia on
 * ainoa poikkeus, ks. A380). Sama perustelu kuplalohkossa alempana.
 *
 * 🔑 YLIAJOKAYTANTO ISTUTUKSEN TEEMALLE (INTEGRATION.md § Isanta-CSS:n eristys):
 *   1. tokenit ensin: `--dg-chat-*` (esim. `:root`) tai `--cw-*` `.cw-wrapper`-lohkossa
 *   2. luokkayliajo: KOPIOI COREN VALITSIN SELLAISENAAN (`.cw-wrapper .cw-X`)
 *      — `theme.css` latautuu coren JALKEEN riippuvuutena, joten sama
 *      spesifisyys voittaa jarjestyksella. Jos jarjestysta ei taata (sivuston
 *      oma teema-CSS), lisaa eteen `body` (`body .cw-wrapper .cw-X`) = aina
 *      askelen coren ylapuolella. Valjaan H8 todentaa molemmat.
 * =========================================================== */
.cw-wrapper .cw-history-btn,
.cw-wrapper .cw-panel-new-btn,
.cw-wrapper .cw-panel-save-btn,
.cw-wrapper .cw-close-btn,
.cw-wrapper .cw-sidebar-close,
.cw-wrapper .cw-overlay-close,
.cw-wrapper .cw-ele-action,
.cw-wrapper .cw-ele-dismiss,
.cw-wrapper .cw-input-action,
.cw-wrapper .cw-view-prev,
.cw-wrapper .cw-view-next,
.cw-wrapper .cw-view-reopen,
.cw-wrapper .cw-send-btn,
.cw-lead-form button {
    -webkit-appearance: none;
    appearance: none;
    margin: 0;
    box-shadow: none;
}

/* ===========================================================
 * WRAPPER — fixed overlay alareunassa
 * Lepotila: pelkka input-palkki. Aktiivinen: viestipaneeli inputin ylapuolella.
 * =========================================================== */
.cw-wrapper {
    position: fixed;
    /* ALAREUNAN VAISTO (task 279) — bottom, EI z-index.
     * Z-indexilla nostaminen piilottaisi evastebannerin alleen: sivu nayttaisi
     * peittavan pakollisen ilmoituksen. Widget siirtyy ylemmas, molemmat nakyvat.
     *
     * YLIAJON RESOLVOI CSS, EI JS. Ketju on tahallinen:
     *   --cw-dock-offset  = teeman yliajo. Mika tahansa yksikko: 3rem, calc(),
     *                       env(safe-area-inset-bottom). VOITTAA aina.
     *   --cw-dock-auto    = widgetin havainnoima offset. JS kirjoittaa AINA px.
     *   0px               = kumpaakaan ei ole → nykykaytos, ei regressiota.
     *
     * Miksi nain eika "JS lukee --cw-dock-offsetin ja paattaa": custom propertyn
     * computed value on token-MERKKIJONO, ei resolvoitu px — getPropertyValue
     * palauttaa kirjaimellisesti " 3rem". JS-reitti vaatisi rem/calc-parserin,
     * joka hajoaa hiljaa. var()-fallback antaa "yliajo voittaa" ilmaiseksi.
     *
     * 🔴 Jos lisaat .cw-wrapperille bottom-saannon johonkin media queryyn, KAYTA
     * TATA KETJUA. Pelkka `bottom: 0` siella tekee vaistosta inertin juuri siella
     * — ks. mobiiliosion sama saanto (<576px). */
    bottom: var(--cw-dock-offset, var(--cw-dock-auto, 0px));
    left: 0;
    right: 0;
    z-index: var(--cw-z);
    display: flex;
    flex-direction: column;
    align-items: center;
    pointer-events: none; /* salli klikkaus lapimenoa taustaan */
}

/* Aktiivinen: taustan himmennys kun viestipaneeli auki */
.cw-wrapper.cw-active::before {
    content: '';
    position: fixed;
    inset: 0;
    background: var(--cw-overlay);
    pointer-events: auto;
    z-index: -1;
}

/* ===========================================================
 * SAAPUMISANIMAATIO (task 281) — docki nousee paikalleen kerran istunnossa
 *
 * TAVOITE: kavija huomaa syotekentan. Liikutetaan koko dockia, EI kirjaimia:
 * aareisnako ei havaitse tekstin sisaista liiketta 46px:n palkin sisalla, se
 * havaitsee ison muodon liikkeen alkamisen.
 *
 * KETJU ON KAKSI ANIMAATIOTA: docki laskeutuu → (tauko) → lahetysnappi valahtaa.
 * Lahde (Farizonin teema 27.8.2026) ajoi kolmea: valissa oli placeholderin
 * pyyhkaisy, joka EI siirry aitiin — aidissa placeholder kirjoittuu merkki
 * kerrallaan (setPlaceholder) eika pyyhkaisy sovi sen paalle.
 *
 * 🔑 LEPOTILASSA EI JAA TRANSFORMIA (ei `forwards`-tayttoa). Transformoitu
 * elementti on `fixed`-jalkelaistensa sijoitussailio, ja wrapperilla on yksi:
 * `.cw-wrapper.cw-active::before` (taustan himmennys, inset: 0). Pysyva
 * transform kutistaisi himmennyksen wrapperin laatikkoon koko ruudun sijaan.
 * JS perua saapumisen myos silloin kun kavija avaa chatin kesken animaation.
 *
 * 🔑 `backwards`-taytto hoitaa odotuksen: 0%-ruutu on voimassa jo viiveen ajan,
 * joten dockia ei tarvitse erikseen piilottaa omalla luokalla.
 *
 * Kerran per istunto (JS, sessionStorage): chat on joka sivulla ja palautuu
 * navigoinnin yli, joten joka latauksella soiva saapuminen olisi tapettia.
 * =========================================================== */

/* 🔑 Keyframet eika siirtyma, koska ylityksen maara halutaan pikseleina.
   `cubic-bezier`illa ylitys on sidottu matkaan — back-out antaisi ~5px eika
   sita voi saataa erikseen. Keyframella ylitys on oma tokeninsa. */
.cw-wrapper.cw-arriving {
    animation: cw-arrive-dock var(--cw-arrive-duration) var(--cw-arrive-easing) var(--cw-arrive-delay) backwards;
}

@keyframes cw-arrive-dock {
    0%   { transform: translateY(var(--cw-arrive-travel)); opacity: 0; }
    55%  { opacity: 1; }
    /* Ylitys: docki kay lepotilansa ylapuolella ja laskeutuu takaisin. */
    70%  { transform: translateY(calc(-1 * var(--cw-arrive-overshoot))); }
    100% { transform: translateY(0); }
}

/* Varjo kirkastuu laskeutumisen ajaksi ja palaa lepoarvoonsa.
 *
 * 🔴 KOHDE ON .cw-input-inner, EI .cw-input-row. Lahteessa varjo oli
 * syoterivilla; aidissa syoterivi on pelkka paddingkehys ja varjo
 * (`--cw-shadow-sm`) on kapselilla. Sama animaatio, eri elementti.
 *
 * 🔴 KESTO JA VIIVE = NOUSUN OMAT. Lahteessa varjolla oli oma kesto (0.45s) ja
 * viive 0, mika tarkoitti etta se soi 0–450ms eli kokonaan ennen kuin docki
 * edes lahti liikkeelle (600ms) — efekti oli 100-prosenttisesti nakymaton.
 * Sidonta nousun ajoitukseen korjaa sen eika voi ajautua siita eroon.
 *
 * `backwards` eika `both`: eteenpain tayttava animaatio jaadyttaisi kapselin
 * oman `transition: box-shadow` -siirtyman (:focus-within) luokan ajaksi. */
.cw-wrapper.cw-arriving .cw-input-inner {
    animation: cw-arrive-landing-shadow var(--cw-arrive-duration) ease-out var(--cw-arrive-delay) backwards;
}

@keyframes cw-arrive-landing-shadow {
    from { box-shadow: var(--cw-arrive-shadow); }
    to   { box-shadow: var(--cw-shadow-sm); }
}

/* Lahetysnapin valahdys kehotuksena — ketjun toinen ja viimeinen animaatio.
 *
 * 🔑 VALAHDYS ON KAANTEINEN, EI PELKKA VALKOINEN TAUSTA. Napin ikoni on
 * `--cw-on-primary` ja syoterivi on valkoinen, joten pelkka vaalea tausta
 * tekisi napista huipun ajaksi nakymattoman: valkoinen laatikko valkoisella
 * rivilla, valkoinen ikoni. Se luettaisiin napin katoamiseksi, ei kehotukseksi.
 * Siksi huipussa vaihtuvat YHDESSA tausta ja ikonin vari, napin OMILLA
 * kahdella tokenilla — ei kolmatta savya.
 *
 * 🔴 `opacity: 1` on AIDIN OMA POIKKEAMA LAHTEESTA, ei kopiointivirhe.
 * Lahteen nappi oli taydella peitolla; aidin `.cw-send-btn` on lepotilassa
 * `opacity: 0.4` (kirkastuu vasta fokuksessa/hoverissa). Varikaanto 40 %:n
 * peitolla valkoista kapselia vasten on niin heikko ettei se tayta kehotuksen
 * tehtavaa. Luokan poiston jalkeen nappi palaa 0.4:aan kapselin olemassa
 * olevan `transition: opacity` -siirtyman kautta eli PEHMEASTI — napsahtava
 * paluu lukisi "meni taas pois paalta".
 *
 * Kaksi pulssia eika yksi: yksittainen valahdys luetaan helposti
 * renderointivirheeksi, toisto tekee siita tarkoituksellisen. Kolme alkaisi jo
 * vaatia huomiota takaisin.
 *
 * 🔴 VIIVE 0.28s, EI LAHTEEN EFEKTIIVISTA 630ms. Lahteessa laskeutumisen ja
 * valahdyksen valiin jai 630ms, mutta placeholderin pyyhkaisy taytti siita
 * 500ms — tauko ei ollut koskaan tyhja. Kahdella animaatiolla 630ms lukee kahtena
 * erillisena tapahtumana: kausaalinen side katkeaa ~400ms:n kohdalla. 280ms on
 * riittavan pitka etta ylitys on asettunut (nousun viimeinen kolmannes ON
 * asettumista, eli wrapper on visuaalisesti paikallaan ~420ms:sta) ja
 * riittavan lyhyt etta valahdys lukee nousun SEURAUKSENA. */
.cw-wrapper .cw-send-btn.cw-arrive-flash {
    animation: cw-arrive-flash var(--cw-arrive-flash-duration) ease-in-out var(--cw-arrive-flash-delay) var(--cw-arrive-flash-count);
}

@keyframes cw-arrive-flash {
    0%, 100% {
        background-color: var(--cw-send-bg);
        color: var(--cw-on-primary);
        opacity: 1;
    }
    50% {
        background-color: var(--cw-on-primary);
        color: var(--cw-send-bg);
        opacity: 1;
    }
}

/* ===========================================================
 * INPUT-RIVI (aina nakyvissa, paalimmaisena panelin edessa)
 * =========================================================== */
.cw-input-row {
    pointer-events: auto;
    width: 100%;
    max-width: var(--cw-panel-max-width);
    padding: 0 0.75rem 0.75rem;
    box-sizing: border-box;
    position: relative;
    z-index: 2;
}

/* Pyoristetty input-palkki (desktop: kapselimalli) */
.cw-input-inner {
    display: flex;
    gap: 0.4rem;
    align-items: flex-end;
    background: var(--cw-surface);
    border: 1px solid var(--cw-border);
    border-radius: var(--cw-radius-capsule);
    padding: 0.45rem 0.5rem 0.45rem 0.9rem;
    box-shadow: var(--cw-shadow-sm);
    transition: box-shadow var(--cw-transition), border-color var(--cw-transition);
}

/* Syotekapselin hahmo (task 403): pieni kasvokuva kapselin vasemmassa reunassa.
 * Syntyy vain dokissa. NAKYY VAIN LEPOTILASSA: kun chat on aktiivinen, iso
 * hahmo on jo ruudulla (nayttamolla tai tervehdyksessa), ja kaksi hahmoa
 * paallekkain olisi saman asian toisto. Aktiivisuus on jo wrapperin luokka,
 * joten piilotus on CSS:n asia eika JS:n toinen tila. */
.cw-input-avatar {
    flex: 0 0 auto;
    align-self: center;
    /* Koko = kapselin sisaltokorkeus (--cw-input-control-size, sama kuin
     * lahetysnapilla), EI omaa lukua (task 419, A17). 2.25rem venytti
     * kapselia napin 2rem:n yli, ja kentta hyppasi kun kuva piilotettiin. */
    width: var(--cw-input-avatar-size);
    height: var(--cw-input-avatar-size);
    object-fit: contain;
}

.cw-wrapper.cw-active .cw-input-avatar {
    display: none;
}

/* ===========================================================
 * ELEPALKKI (aukko A41, spec/ele.md) — input-kapselin YLLA
 *
 * MUOTO: STATUSBAR (Teemu 22.9.2026) — ohut, koko input-rivin levyinen
 * rivi per ele, pinottuna allekkain nostojarjestyksessa (DOM-jarjestys).
 * Ei nappi kuplassa eika kortti virrassa.
 * HIENOVARAINEN: ei varjoa, ei jatkuvaa animaatiota — vain yksi nousu
 * alareunasta nostohetkella (.cw-ele--flash, A448 / Teemu 23.9.). Mobiilissa
 * max-height + vieritys (ks. MOBIILI-lohko), jotta useampi ele ei koskaan
 * tyonna input-kenttaa pois nakyvista.
 * Istutus saa yliajaa tokeneilla (kerros 3), ei tata tiedostoa muokkaamalla.
 * =========================================================== */
.cw-actionbar {
    display: flex;
    flex-direction: column;
    gap: var(--cw-ele-gap, 0.25rem);
    margin: 0 0 var(--cw-ele-gap, 0.25rem);
    max-height: var(--cw-ele-max-height, 6.5rem);
    overflow-y: auto;
}
.cw-actionbar[hidden] {
    display: none;
}
/* Palkki piiloon kun chat on kiinni (task 419, A16 — Antin linjaus 23.9.).
 * Palkki on syoterivin lapsi, ja syoterivi jaa nakyviin chatin sulkeutuessa
 * -> palkki kellui sivun paalla. Palaa kun chat avataan (.cw-active).
 *
 * 🔴 `:not([data-cw-mode="embed"])` ON PAKOLLINEN: upotetussa moodissa
 * `.cw-active`-luokkaa EI aseteta koskaan (chat-widget.js: vain openPanel()
 * lisaa sen, ja upotettu asettaa pelkan state.isOpen-lipun buildDOM:ssa).
 * Ilman rajausta upotetun chatin elepalkki olisi pysyvasti piilossa. */
.cw-wrapper:not(.cw-active):not([data-cw-mode="embed"]) .cw-actionbar {
    display: none;
}
.cw-ele {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    width: 100%;
    box-sizing: border-box;
    min-height: 1.9rem;
    padding: 0.1rem 0.25rem 0.1rem 0.75rem;
    background: var(--cw-ele-bg, var(--cw-surface));
    color: var(--cw-ele-text, var(--cw-text));
    border: 1px solid var(--cw-ele-border, var(--cw-border));
    border-radius: var(--cw-ele-radius, var(--cw-radius-lg));
    font-size: var(--cw-ele-font-size, 0.85rem);
    line-height: 1.3;
}
/* Nousu (A448, Teemu 23.9. klo 12:22: "nousta alareunasta ylos etta katse
 * kiinnittyy"; kumoaa task 418:n 6d-renkaan). JS lisaa luokan VAIN uudelle
 * nostolle (ei sessionStorage-palautukselle), animaatio ajetaan kerran eika
 * toistu, ja JS poistaa luokan animationendissa.
 * Liike on lyhyt ja kerran: katse kiinnittyy, mutta mikaan ei syki. */
.cw-ele--flash {
    animation: cw-ele-rise var(--cw-ele-flash-duration) cubic-bezier(0.2, 0.8, 0.2, 1) 1;
}
@keyframes cw-ele-rise {
    from { transform: translateY(100%); opacity: 0; }
    to { transform: none; opacity: 1; }
}
/* Palkki on vierityssailio (overflow-y: auto). Transform laajentaa sen
 * vieritettavaa aluetta, joten nousun ajaksi ilmestyisi vierityspalkki.
 * hidden leikkaa ilman palkkia: ele nousee palkin alareunan takaa, eli
 * input-kentan ylareunasta. Vain liikkeen salliessa — reduced motionilla
 * animaatiota ei ajeta, animationend ei laukea, eika luokka poistuisi,
 * joten palkki jaisi vierittamattomaksi. */
@media (prefers-reduced-motion: no-preference) {
    .cw-actionbar:has(> .cw-ele--flash) {
        overflow-y: hidden;
    }
}
/* A711 (Teemu 1.10. RC-live): yhteystietoeleen label + linkit ovat YHTA
 * tekstivirtaa, ei kukin oma flex-lapsensa. Erillisina flex-lapsina jokainen
 * kutistui omaksi sarakkeekseen (overflow-wrap: anywhere -> linkin
 * min-content = yksi merkki), ja numero nakyi omana palstanaan. Body vie
 * tilan X:n vasemmalta puolelta; sen sisalla linkit rivittyvat kuin sanat. */
.cw-ele-body {
    flex: 1 1 auto;
    min-width: 0;
}
/* inline-block: numero pysyy yhtena kokonaisuutena ja siirtyy tarvittaessa
 * kokonaan seuraavalle riville; max-width + overflow-wrap rivittaa vain
 * rivia leveamman (pitka sahkoposti 375 px:ssa). */
.cw-wrapper .cw-ele-body > .cw-ele-action,
.cw-wrapper .cw-ele-body > .cw-ele-value {
    display: inline-block;
    max-width: 100%;
    box-sizing: border-box;
    vertical-align: baseline;
}
.cw-ele-label {
    color: var(--cw-text-muted);
}
/* A568: yhteystiedon numero ilman soitettavaa kohdetta — teksti, ei toiminto.
 * Sama valistys kuin .cw-ele-actionilla, jotta rivi ei hyppaa. */
.cw-ele-value {
    padding: 0.2rem 0.3rem;
    overflow-wrap: anywhere;
}
/* X aina rivin oikeaan reunaan, sisalto vasemmalle. */
.cw-ele > .cw-ele-dismiss {
    margin-left: auto;
}
.cw-wrapper .cw-ele-action {
    appearance: none;
    background: none;
    border: 0;
    padding: 0.2rem 0.3rem;
    font: inherit;
    /* `font: inherit` nollaa painon -> toiminto nayttaa leipatekstilta (A11). */
    font-weight: var(--cw-ele-font-weight);
    color: var(--cw-ele-link, var(--cw-primary));
    text-decoration: none;
    cursor: pointer;
    border-radius: var(--cw-radius);
    overflow-wrap: anywhere;
}
.cw-wrapper .cw-ele-action:hover {
    text-decoration: underline;
}
.cw-wrapper .cw-ele-action:focus-visible,
.cw-wrapper .cw-ele-dismiss:focus-visible {
    outline: none;
    box-shadow: var(--cw-focus-ring);
}
.cw-wrapper .cw-ele-dismiss {
    appearance: none;
    flex: 0 0 auto;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--cw-text-muted);
    /* Ikoni maskina (task 419, A3): sama SVG-ruksi kuin muissa sulkunapeissa. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0;
    line-height: 1;
    cursor: pointer;
    --cw-icon: var(--cw-icon-close);
    --cw-icon-box: var(--cw-ele-dismiss-icon-size);
}
.cw-wrapper .cw-ele-dismiss:hover {
    background: var(--cw-bg);
    color: var(--cw-text);
}

/* FOKUSINDIKAATTORI: kapseli, ei syote (aukko A32, 15.9.2026)
 *
 * TAMA RIVI PALAUTTI ENNEN LEPOTILAN ARVOT (`border-color: var(--cw-border)`
 * + `box-shadow: var(--cw-shadow-sm)`) kommentilla "hillitty, ei korosteta koko
 * pilleria". Lopputulos ei ollut hillitty vaan OLEMATON: syoterivi oli coren
 * ainoa kontrolli ilman fokusindikaattoria, kun kaikilla napeilla se on.
 * Se on WCAG 2.4.7 (Focus Visible) -rike, ei tyylivalinta. Jos joku haluaa
 * hillitympaa, se tehdaan tokenia saatamalla — ei nollaamalla tata saantoa.
 *
 * MIKSI KAPSELI EIKA `.cw-textarea`: kapselilla on `border-radius:
 * var(--cw-radius-capsule)`, syotteella ei ole reunaa lainkaan. Fokusrengas
 * syotteessa piirtyy siksi NELISKULMAISENA pyoristetyn kapselin sisaan — se
 * oli A32:n alkuperainen oire (Teemun havainto hero-lohkosta 15.9.).
 *
 * MIKSI BOX-SHADOW: se seuraa `border-radius`ia automaattisesti, joten sama
 * saanto toimii kaikissa kolmessa muodossa ilman variantteja — tyopoydan
 * kapseli (2rem), mobiilin taysleveys (0) ja upotettu moodi.
 * Istutukset (RC, Farizon, demo) ratkaisivat taman kukin omalla
 * border-color-saannollaan; core kayttaa `--cw-focus-ring`ia koska se on
 * SAMA mekanismi kuin coren kuudella muulla fokussaannolla ja koska
 * brandivari ei ole automaattisesti kontrastiltaan kelpo (ks. token-kommentti).
 *
 * LEPOVARJO SAILYY listalla: ilman sita kapseli menettaisi kohotuksensa
 * fokusoituna. Mobiilissa kapseli on sama pilleri kuin taalla (A705, ei enaa
 * omaa lepovarjo-/reunasaantoa). Alle 576 px MOBIILI-lohkon oma
 * `:focus-within` korvaa renkaan tummemmalla reunalla (Teemu 1.10.2026,
 * --cw-input-border-focus-mobile) — indikaattori vaihtuu, ei katoa.
 *
 * `:focus-within` EIKA `:focus-visible`: fokusoituva elementti on syote, mutta
 * indikaattori kuuluu kapseliin. Tama on ainoa valitsin joka yhdistaa ne kaksi
 * ilman `:has()`-tukioletusta. Seuraus: rengas nakyy myos hiiriklikkauksella —
 * se on tarkoitettu, koska kapseli on kayttajalle "se kentta" ja aktiivisen
 * kentan on nayttava aktiiviselta. Sama kuin istutuksilla (`:focus`-haara). */
.cw-input-inner:focus-within {
    box-shadow: var(--cw-focus-ring), var(--cw-shadow-sm);
}

/* Textarea: reunaton, sulautuu kapselin sisaan, keskitetty vertikaalisti */
.cw-textarea {
    flex: 1;
    border: none;
    outline: none;
    box-shadow: none;
    font-size: 0.85rem;
    font-family: inherit;
    background: transparent;
    color: var(--cw-text);
    padding: 0.15rem 0;
    margin: 0;
    resize: none;
    min-height: 1.4rem;
    max-height: 5rem;
    line-height: 1.4;
    align-self: center;
    /* Isanta-CSS:n eristys (A446). border-box: `min-height` sisaltaa
     * paddingin. Lahes jokainen isanta (Bootstrap, Pico, WP-teemat) asettaa
     * `* { box-sizing: border-box }`, joten tama on se korkeus jonka
     * istutukset jo nayttavat (31 px @ 21 px html); ilman isantaa kentta oli
     * paddingin verran korkeampi (36 px). appearance + pyoristys: isannan
     * textarea-saanto (Pico: none + 5 px). */
    box-sizing: border-box;
    -webkit-appearance: none;
    appearance: none;
    border-radius: 0;
}

/* SYOTTEELLA EI OLE OMAA FOKUSINDIKAATTORIA (aukko A32, Teemu 15.9.2026:
 * "ota se vaikka pois kokonaan, ja jos on joku focus-efekti se pitaa olla
 * tossa .cw-input-inner -elementissa"). Indikaattori on kapselissa (ylla).
 *
 * 🔴 MIKSI KOLME LUOKKAA JA MIKSI `:focus-visible` ON LUETELTAVA ERIKSEEN:
 * tama saanto EI riita muodossa `.cw-textarea:focus`. Se on spesifisyydeltaan
 * (0,2,0), ja isantasivun teema voi asettaa oman fokusrenkaan korkeammalla
 * spesifisyydella. MITATTU, ei paatelty: Digitaali-teema asettaa
 * `body:not(.wp-admin) :focus-visible` -outlinen, joka on (0,2,1) eli VOITTAA.
 * Juuri siita syntyi A32:n neliskulmainen reunus. Istutus (demo) joutui
 * kirjoittamaan oman `body:not(.wp-admin) .cw-textarea:focus-visible`
 * -vastasaannon — eli korjaus oli kolmessa istutuksessa ja puuttui aidista.
 *   `.cw-input-inner .cw-textarea` = (0,3,0) > (0,2,1) -> core voittaa
 *   isantateeman ILMAN isantakohtaista valitsinta (`body:not(.wp-admin)` on
 *   WP-spesifi eika kuulu kerros 1:een). `!important` tarvitaan vasta
 *   isannan omaa `!important`ia vastaan (A710, alla).
 *
 * `:focus` JA `:focus-visible` MOLEMMAT: `:focus` yksin ei nollaa isannan
 * `:focus-visible`-haaraa, koska kaskade valitaan saantokohtaisesti eika
 * "vahvin tila voittaa" -periaatteella. Ohjelmallinen `.focus()` EI laukaise
 * `:focus-visible`-haaraa — siksi oiretta ei saatu toistettua automaatiolla.
 *
 * Kapselin `:focus-within` on (0,2,0) eika silla ole samaa ongelmaa: se ei
 * kilpaile isannan outline-saannon kanssa vaan lisaa oman box-shadowin.
 *
 * 🔴 SPESIFISYYS EI RIITA ISANNAN `!important`ia VASTAAN (aukko A710, PJ
 * 2.10.2026 Chrome MCP, rengascenter.fi). RC:n teeman `css/common.css`:
 *   `textarea:focus { outline-offset:-2px; outline: 2px solid
 *    var(--wp--preset--color--primary-dark, #B5C102) !important; }`
 * `!important` voittaa spesifisyydesta riippumatta, joten (0,3,0)-nollaus
 * havisi (0,1,1)-saannolle ja textarean (border-radius 0) ymparille piirtyi
 * neliskanttinen rengas kapselin sisaan tyopoydalla. Siksi outline ja
 * box-shadow kantavat `!important`in: kahden `!important`-julistuksen kesken
 * ratkaisee taas spesifisyys, ja (0,3,0) > (0,1,1) -> core voittaa. Sama
 * peruste kuin kuplasaannoissa (A380). Tama EI ole istutus-vs-core-sotaa:
 * isantateema kayttaa `!important`ia, eika istutuksella ole syyta yliajaa
 * syotteen fokusta (indikaattori on kapselissa, saadettava tokenilla
 * --cw-focus-ring). outline-offset jatetaan: outline none -> offset ei piirra.
 *
 * Pico.css: --pico-border-color on tuntematon custom property muilla
 * alustoilla = no-op. */
.cw-input-inner .cw-textarea:focus,
.cw-input-inner .cw-textarea:focus-visible {
    border: none;
    outline: none !important;
    box-shadow: none !important;
    --pico-border-color: transparent;
}

/* Send-nappi: pyorea, nuoli ylos — pysyy kapselin alareunassa */
.cw-wrapper .cw-send-btn {
    background: var(--cw-send-bg);
    color: var(--cw-on-primary);
    border: none;
    /* Sama token kuin kasvokuvalla (A17): nappi maaraa kapselin korkeuden. */
    width: var(--cw-input-control-size);
    height: var(--cw-input-control-size);
    /* 0: isannan button-padding (Pico .75rem 1rem) soi kiintean kokoisen
     * napin sisaltoalueen negatiiviseksi (A446). Ikoni keskitetaan flexilla. */
    padding: 0;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
    font-size: 1rem;
    line-height: 1;
    opacity: 0.4;
    transition: opacity var(--cw-transition);
    align-self: flex-end;
}

/* Send-nappi kirkkaampi kun input on fokuksessa */
.cw-wrapper .cw-input-inner:focus-within .cw-send-btn {
    opacity: 1;
}

.cw-wrapper .cw-send-btn:hover {
    opacity: 1;
}

.cw-wrapper .cw-send-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.cw-wrapper .cw-send-btn:focus-visible {
    box-shadow: var(--cw-focus-ring);
}

/* Stop-tila (A671 osa 2b): lahetyksen ajan nappi pysayttaa vastauksen.
 * Nuoli (napin tekstisisalto) piiloon font-size 0:lla, neliö ::before:lla.
 * Taysi nakyvyys myos ilman fokusta: kavija etsii nappia vastausta odottaessa.
 * Istutus saa yliajaa koon tokenilla --cw-stop-icon-size. */
.cw-wrapper .cw-send-btn.cw-send-btn--stop {
    font-size: 0;
    opacity: 1;
}

.cw-wrapper .cw-send-btn.cw-send-btn--stop::before {
    content: '';
    display: block;
    width: var(--cw-stop-icon-size, 0.75rem);
    height: var(--cw-stop-icon-size, 0.75rem);
    background: currentColor;
    border-radius: 2px;
}

/* ===========================================================
 * INPUT ACTIONS — textarean vasemmalla puolella
 * Core-API: chat-widget.js addInputAction({icon, label, className, onClick}).
 * Projektikohtaiset variantit (esim. .cw-upload-btn) maaritellaan projektin
 * omassa CSS:ssa — tassa on vain geneerinen pohja jonka core tuottaa.
 * Variantin valitsin on `.cw-wrapper .cw-upload-btn` (0,2,0): paljas
 * `.cw-upload-btn` haviaisi taman saannon spesifisyydelle (A468).
 * =========================================================== */
.cw-wrapper .cw-input-action {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--cw-text-muted);
    font-size: 1.1rem;
    line-height: 1;
    padding: 0.25rem;
    flex-shrink: 0;
    min-width: 2rem;
    min-height: 2rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    transition: background var(--cw-transition-fast), color var(--cw-transition-fast);
    align-self: flex-end;
    margin-bottom: 0.1rem;
}

.cw-wrapper .cw-input-action:hover {
    background: var(--cw-bg);
    color: var(--cw-text);
}

.cw-wrapper .cw-input-action:focus-visible {
    box-shadow: var(--cw-focus-ring);
}

/* ===========================================================
 * VIESTIPANEELI (aktiivinen keskustelu)
 * Panel liukuu kapseli-inputin taakse: padding-bottom tekee tilaa,
 * negatiivinen margin-bottom vetaa panelin kapselin alle.
 * =========================================================== */
.cw-panel {
    pointer-events: auto;
    width: 100%;
    max-width: var(--cw-panel-max-width);
    max-height: var(--cw-panel-max-height);
    background: var(--cw-surface);
    border: var(--cw-panel-border);   /* A13: koukku, ks. tokenilohko */
    border-bottom: none;
    border-radius: var(--cw-radius-panel) var(--cw-radius-panel) 0 0;
    box-shadow: var(--cw-shadow-panel);
    display: flex;
    flex-direction: column;
    margin: 0 0.75rem;
    /* Limitys SEURAA syoterivin mitattua korkeutta, ei vakiota (aukko A47).
     * Perustelu ja mittausdata: --cw-panel-dock-overlap tokenilohkossa. */
    margin-bottom: calc(-1 * var(--cw-panel-dock-overlap));
    padding-bottom: var(--cw-panel-dock-overlap);
    box-sizing: border-box;
    position: relative;
    z-index: 1;
}

/* A43 osa 3 (Teemu 17.9.2026): KIINTEA IKKUNA, SISALTO VIERII SEN SISALLA.
 *
 * 🔴 MIKSI TAMA ON OSA SAMAA MALLIA KUIN OSAT 1–2, EI ERILLINEN TYYLISEIKKA:
 * osien 1–2 saanto on "ikkuna liikkuu vain kayttajan toiminnasta". Jos paneeli
 * kasvaa sisallon mukana, KOON MUUTOS ON ITSESSAAN LIIKETTA — eli saanto on
 * mahdoton toteuttaa kasvavalla paneelilla, vaikka scrollTop ei muuttuisi
 * pikselillakaan. Teemu 16.9.: "tää sisältö kontroloi ikkunaa on just niiku väärin."
 *
 * Kolme tilaa:
 *   lepotila      paneeli kiinni (.cw-hidden), vain syoterivi nakyy
 *   fokus         paneeli auki LUONTAISELLA korkeudellaan = matala, tervehdys nakyy
 *                 (ei omaa saantoa: `max-height` yksin riittaa, sisaltoa on vahan)
 *   lahetetty     tama luokka → korkeus lukitaan maksimiin kerralla, ei muutu enaa
 *
 * KORKEUS = `--cw-panel-max-height`, EI uutta tokenia. Paneelin maksimi on jo
 * tuo arvo (A695: enintaan 32rem, rajattu viewporttiin); tama saanto ei muuta MITA maksimi on vaan sen ETTA sinne
 * paastaan heti. Uusi token tekisi samasta mitasta kaksi totuutta ja istutus
 * saataisi vain toista — eli paneelin maksimi ja "taysi" korkeus ajautuisivat
 * erilleen ensimmaisessa teemayliajossa. `height` == `max-height` on tarkoitus.
 *
 * 🔴 EI UPOTETUSSA MOODISSA. `:not([data-cw-mode="embed"])` on RAKENTEELLINEN
 * portti, ei JS:n varassa oleva sopimus: upotettu paneeli saa korkeutensa
 * sailiolta (`flex: 1 1 auto`), eika silla ole "kiinni"-tilaa jota vasten
 * "taysi" olisi edes maariteltavissa. Jos JS joskus lisaisi luokan vahingossa
 * myos upotettuun, tama saanto ei silti osu. Vrt. embedin oma saantolohko alla.
 * (Aukko A12 on laskeutunut coreen 15.9. EIKA sita ole todennettu selaimessa —
 * sen asemointiin ei kosketa taman taskin puitteissa.)
 */
.cw-wrapper:not([data-cw-mode="embed"]) .cw-panel--full {
    height: var(--cw-panel-max-height);
}

/* Paneelin header (aukko A93): historia (vasen) + chatin nimi + uusi keskustelu
   + sulje (oikea ryhma).

   🔴 EI `justify-content: space-between` eika `--solo`-modifieria (poistettu
   task 344). space-between jakaa vapaan tilan lapsien VALIIN, joten sen tulos
   riippuu lapsimaarasta: nelja lasta levisi tasavalein yli koko rivin ja
   yhdella lapsella sulkunappi valui vasempaan. Modifier korjasi jalkimmaisen
   ehdollisesti — ja ehdollinen asettelusaanto on juuri se joka unohtuu kun
   seuraava lapsi lisataan.

   Nyt saanto on ehdoton: otsikko kasvaa vapaaseen tilaan ja
   `.cw-panel-new-btn`in `margin-left: auto` tyontaa oikean ryhman oikeaan
   reunaan. Se pitaa kaikissa neljassa yhdistelmassa (dokki/upotettu x
   otsikko on/ei, historia on/ei), koska uusi-keskustelu-nappi on AINA. */
.cw-panel-header {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    padding: var(--cw-panel-header-padding);
    min-height: var(--cw-panel-header-min-height);
    background: var(--cw-panel-header-bg);
    /* Matalassa paneelissa flex-sarake kutistaisi palkin (A12). */
    flex-shrink: 0;
}

/* IKONINAPIT (task 419, A1–A3): kolme otsikkopalkin nappia, overlayn ja eleen
 * sulkunappi. Glyfiteksti jaa DOMiin (JS ei muutu), mutta `font-size: 0`
 * piilottaa sen; nakyva ikoni on `::before`-maski samasta SVG-sarjasta
 * (tokenit --cw-icon-*). Napin nimi tulee `aria-label`ista, ei glyfista. */
.cw-history-btn::before,
.cw-panel-new-btn::before,
.cw-close-btn::before,
.cw-sidebar-close::before,
.cw-overlay-close::before,
.cw-ele-dismiss::before {
    content: "";
    display: block;
    flex: none;
    width: var(--cw-icon-box);
    height: var(--cw-icon-box);
    background-color: currentColor;
    -webkit-mask: var(--cw-icon) center / contain no-repeat;
    mask: var(--cw-icon) center / contain no-repeat;
}

/* Kolme otsikkonappia samaan kiinteaan ruutuun: glyfeilla ne olivat
 * 27 / 22 / 29 px leveita. margin 0: isannan button-saanto (Pico:
 * margin-bottom 1rem) venyttaisi palkkia (vrt. overlayn sulkunappi, task 418). */
.cw-wrapper .cw-history-btn,
.cw-wrapper .cw-panel-new-btn,
.cw-wrapper .cw-close-btn,
.cw-wrapper .cw-sidebar-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 var(--cw-header-btn-size);
    width: var(--cw-header-btn-size);
    height: var(--cw-header-btn-size);
    margin: 0;
    padding: 0;
    font-size: 0;
    line-height: 1;
    border-radius: var(--cw-radius);
    --cw-icon-box: var(--cw-header-icon-size);
}

.cw-wrapper .cw-history-btn { --cw-icon: var(--cw-icon-menu); }
.cw-wrapper .cw-panel-new-btn { --cw-icon: var(--cw-icon-plus); }
.cw-wrapper .cw-close-btn {
    --cw-icon: var(--cw-icon-close);
    --cw-icon-box: var(--cw-header-close-icon-size);
}

/* Chatin nimi. Kasvaa vapaaseen tilaan (= tyontaa oikean ryhman oikealle myos
   ilman auto-marginaalia) ja katkeaa kolmella pisteella kapealla ruudulla —
   istutuksen nimi voi olla pitka eika se saa pakottaa headeria rivittymaan.
   min-width: 0 on pakollinen: flex-lapsen oletus `min-width: auto` estaisi
   kutistumisen sisallon alle ja ellipsi jaisi laukeamatta. */
.cw-panel-title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--cw-text);
    line-height: 1.4;
}

/* "Uusi keskustelu" (+). Sama hillitty ikoninapin ilme kuin historia- ja
   sulkunapilla — se on headerin kolmas ikoni, ei kutsu toimintaan.
   margin-left: auto = oikean ryhman ankkuri, ks. .cw-panel-header ylla. */
.cw-wrapper .cw-panel-new-btn {
    margin-left: auto;
    background: none;
    border: none;
    cursor: pointer;
    color: var(--cw-text-muted);
}

.cw-wrapper .cw-panel-new-btn:hover {
    color: var(--cw-text);
}

.cw-wrapper .cw-panel-new-btn:focus-visible {
    box-shadow: var(--cw-focus-ring);
    border-radius: var(--cw-radius);
}

/* KESKUSTELUN TALLENNUS (A427, task 434). Oma lohko eika lisays ylla
   oleviin valitsinlistoihin, jotta rinnakkainen otsikkonappityo ei tormaa
   siihen. Sama ikoninapin ilme kuin uusi-keskustelu-napilla; ikoni on
   tokeni `--cw-icon-download`, jonka istutus voi asettaa. Asettuu oikeaan
   ryhmaan uusi-napin jalkeen (sen margin-left: auto ankkuroi ryhman). */
.cw-wrapper .cw-panel-save-btn {
    --cw-icon: var(--cw-icon-download, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2z'/%3E%3C/svg%3E"));
    --cw-icon-box: var(--cw-header-icon-size);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 var(--cw-header-btn-size);
    width: var(--cw-header-btn-size);
    height: var(--cw-header-btn-size);
    margin: 0;
    padding: 0;
    font-size: 0;
    line-height: 1;
    background: none;
    border: none;
    border-radius: var(--cw-radius);
    cursor: pointer;
    color: var(--cw-text-muted);
}

.cw-panel-save-btn::before {
    content: "";
    display: block;
    flex: none;
    width: var(--cw-icon-box);
    height: var(--cw-icon-box);
    background-color: currentColor;
    -webkit-mask: var(--cw-icon) center / contain no-repeat;
    mask: var(--cw-icon) center / contain no-repeat;
}

.cw-wrapper .cw-panel-save-btn:hover {
    color: var(--cw-text);
}

.cw-wrapper .cw-panel-save-btn:focus-visible {
    box-shadow: var(--cw-focus-ring);
}

.cw-wrapper .cw-close-btn {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--cw-text-muted);
}

.cw-wrapper .cw-close-btn:hover {
    color: var(--cw-text);
}

.cw-wrapper .cw-close-btn:focus-visible {
    box-shadow: var(--cw-focus-ring);
    border-radius: var(--cw-radius);
}

/* T109: Historia-nappi (hamburger) */
.cw-wrapper .cw-history-btn {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--cw-text-muted);
}

.cw-wrapper .cw-history-btn:hover {
    color: var(--cw-text);
}

.cw-wrapper .cw-history-btn:focus-visible {
    box-shadow: var(--cw-focus-ring);
    border-radius: var(--cw-radius);
}

/* ===========================================================
 * SIDEBAR — keskusteluhistoria (T109)
 * =========================================================== */
.cw-sidebar {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--cw-surface);
    z-index: 10;
    display: flex;
    flex-direction: column;
    border-radius: inherit;
}

/* Sivupalkin otsikkorivi = paneelin otsikkopalkin kaksonen (A555): sama
   pehmuste, tausta ja korkeus tokeneista, joten istutuksen teema tyylittaa
   molemmat kerralla. Otsikko (.cw-sidebar-title) kasvaa vapaaseen tilaan ja
   tyontaa sulkunapin oikeaan reunaan, samaan kohtaan kuin paneelin ✕. */
.cw-sidebar-header {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    padding: var(--cw-panel-header-padding);
    min-height: var(--cw-panel-header-min-height);
    background: var(--cw-panel-header-bg);
    border-bottom: 1px solid var(--cw-border);
    flex-shrink: 0;
}

/* Sama ilme kuin paneelin otsikolla (.cw-panel-title). */
.cw-sidebar-title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--cw-text);
    line-height: 1.4;
}

/* Ikoni maskina (A457): sama SVG-ruksi ja koko kuin paneelin sulkunapissa
 * (.cw-close-btn), ei fonttiglyfia jonka koko ja muoto riippuu isannan
 * fontista. Glyfi jaa DOMiin, font-size 0 piilottaa sen; nimi aria-labelista.
 * Ruutu (koko, pehmuste, pyoristys) otsikkonappien yhteisesta saannosta (A555). */
.cw-wrapper .cw-sidebar-close {
    margin-left: auto;
    background: none;
    border: none;
    /* Eksplisiittinen: isannan button-saanto pyoristaa (Pico 5 px, A446). */
    border-radius: 0;
    cursor: pointer;
    color: var(--cw-text-muted);
    --cw-icon: var(--cw-icon-close);
    --cw-icon-box: var(--cw-header-close-icon-size);
}

.cw-wrapper .cw-sidebar-close:hover {
    color: var(--cw-text);
}

/* Rivien valit, pehmuste, pyoristys ja erotin tokeneina (A556). */
.cw-conversation-list {
    flex: 1;
    overflow-y: auto;
    overscroll-behavior: contain;   /* A704: ks. .cw-messages */
    padding: var(--cw-conversation-list-padding);
}

/* Vali marginaalilla eika flex-gapilla: flex-sarake kutistaisi rivit
   vierityvassa listassa (flex-shrink), ja lista on vierityva. */
.cw-conversation-item + .cw-conversation-item {
    margin-top: var(--cw-conversation-gap);
}

.cw-conversation-item {
    padding: var(--cw-conversation-padding);
    border-radius: var(--cw-conversation-radius);
    cursor: pointer;
    border-bottom: var(--cw-conversation-border);
    transition: background var(--cw-transition-fast);
}

.cw-conversation-item:hover {
    background: var(--cw-bg);
}

.cw-conversation-item.cw-conversation-active {
    background: var(--cw-bg);
    border-left: 3px solid var(--cw-primary);
    padding-left: calc(0.75rem - 3px);
}

.cw-conversation-title {
    font-size: 0.8rem;
    color: var(--cw-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.4;
}

.cw-conversation-meta {
    font-size: 0.7rem;
    color: var(--cw-text-muted);
    margin-top: 0.1rem;
}

.cw-conversation-empty {
    padding: 1.5rem 0.75rem;
    text-align: center;
    color: var(--cw-text-muted);
    font-size: 0.8rem;
}

/* ===========================================================
 * VIESTIALUE + KUPLAT
 * =========================================================== */
.cw-messages {
    flex: 1;
    overflow-y: auto;
    /* A704 (Teemu 1.10.2026, DEMO): vierityksen paatyttya ylos/alas ele EI
     * valu taustasivulle (scroll chaining) — sivu ei liiku chatin alla.
     * `contain` eika `none`: elementin oma pomppu/pull-to-refresh-efekti
     * saa jaada, vain ketjutus katkeaa. */
    overscroll-behavior: contain;
    padding: var(--cw-messages-padding);   /* A14 */
    display: flex;
    flex-direction: column;
    gap: var(--cw-messages-gap);
}

/* Alatilan tayte (A43, 16.9.2026): tyhja alusta jonka varassa viimeisin
 * KAYTTAJAN viesti paasee ikkunan ylareunaan. Korkeuden asettaa JS
 * (updateBottomSpacer) — tassa on vain se mita JS ei voi kertoa:
 *
 *   flex: 0 0 auto  — .cw-messages on flex-sarake, ja oletusarvoinen flex-shrink
 *                     kutistaisi taytteen nollaan juuri silloin kun sita eniten
 *                     tarvitaan (sisaltoa on vahan). Ilman tata JS:n asettama
 *                     korkeus on pelkka toive.
 *   min-height: 0   — kumoaa flex-itemin auto-minimin, jotta kutistus nollaan
 *                     onnistuu kun oikeaa sisaltoa on jo ikkunallinen.
 */
.cw-bottom-spacer {
    flex: 0 0 auto;
    min-height: 0;
    height: 0;
    pointer-events: none;
}

.cw-bubble {
    max-width: 85%;
    padding: 0.6rem 0.9rem;
    border-radius: var(--cw-radius-lg);
    font-size: 0.9rem;
    line-height: var(--cw-bubble-line-height);
    word-break: break-word;
}

.cw-bubble-user {
    align-self: flex-end;
    background: var(--cw-bubble-user);
    color: var(--cw-bubble-user-text);
    border-bottom-right-radius: 0.2rem;
}

.cw-bubble-assistant {
    align-self: flex-start;
    background: var(--cw-bubble-assistant);
    color: var(--cw-bubble-assistant-text);
    border-bottom-left-radius: 0.2rem;
    box-shadow: var(--cw-shadow-xs);
}

.cw-bubble-system {
    align-self: center;
    background: var(--cw-success-bg);
    color: var(--cw-success);
    font-size: 0.8rem;
    padding: 0.4rem 0.75rem;
    border-radius: 1rem;
}

/* Pienemmat kuplat widgetissa (paneeli on kapeampi kuin full-page-chat) */
.cw-messages .cw-bubble {
    font-size: 0.8rem;
    /* A14: koukku — sama valitsin istutuksessa havisi latausjarjestyksessa. */
    padding: var(--cw-bubble-padding);
}

/* ===========================================================
 * MYYJAN KUPLA (takeover, task 215a)
 *
 * Rakenne (decorateAgentBubble): .cw-bubble.cw-bubble-assistant.cw-bubble-agent
 *   > .cw-bubble-avatar   (kuva tai kirjain; --spacer kun sama puhuja perakkain)
 *   > .cw-bubble-body     (> .cw-bubble-who + backendin sanitoima sisalto)
 *
 * Botin kupla (decorateBotBubble, A94) on SAMA rakenne ilman .cw-bubble-who:ta
 * ja ilman .cw-bubble-agent-luokkaa. 🔴 Se syntyy VAIN kun istutus on antanut
 * `botAvatar`in — ilman sita kupla on paljas .cw-bubble.cw-bubble-assistant
 * kuten ennen A94:aa, eika mikaan alla olevista saannoista osu siihen.
 *
 * Miksi kaare .cw-bubble-body: kupla on flex-rivi, ja ilman kaaretta jokainen
 * backendin <p> olisi oma flex-itemi vierekkain.
 * =========================================================== */
/* Asettelu on OMASSA luokassaan (.cw-bubble--avatar), koska sen saa myos botin
   kupla kun istutus on asettanut `botAvatar`in (aukko A94, 21.9.2026) — ja
   botin kupla ei saa teeskennella olevansa myyjan kupla: istutusten theme.css
   kohdistaa `.cw-bubble-agent`iin myyjakohtaisia tyyleja.
   `.cw-bubble-agent` SAILYY valitsimessa: myyjan kirjoitusindikaattori
   (applyAgentTyping) rakentaa kuplansa itse eika kulje setBubbleAvatarin
   kautta, joten se saa asettelunsa tata kautta. */
.cw-bubble-agent,
.cw-bubble--avatar {
    display: flex;
    align-items: flex-start;
    gap: var(--cw-bubble-avatar-gap);   /* A14 */
}

.cw-bubble-agent .cw-bubble-body,
.cw-bubble--avatar .cw-bubble-body {
    min-width: 0;   /* pitkat sanat katkeavat kuplan sisalla, eivat levita sita */
}

/* A699 (Teemu 1.10.2026, toinen kerta): hahmo seisoo kuplan VIERESSA, ei
 * kuplassa — sama muoto kuin tervehdyksessa (.cw-welcome--avatar, 22.9.).
 * Ulompi .cw-bubble on vain asettelurivi [hahmo | teksti]; kuplan tausta,
 * varjo, pyoristys ja pehmuste ovat tekstikaareella .cw-bubble-body.
 * Valitsimet kantavat .cw-bubble-assistant-luokan, jotta ne voittavat
 * .cw-bubble-assistant- ja .cw-messages .cw-bubble -saannot jarjestyksesta
 * riippumatta. Myyjan kupla (.cw-bubble-agent) on sama rakenne -> sama muoto.
 * Ilman hahmoa (paljas kupla) mikaan tasta ei osu. */
.cw-bubble-assistant.cw-bubble--avatar,
.cw-bubble-assistant.cw-bubble-agent {
    background: none;
    box-shadow: none;
    border-radius: 0;
    padding: 0;
}

.cw-messages .cw-bubble-assistant.cw-bubble--avatar,
.cw-messages .cw-bubble-assistant.cw-bubble-agent {
    padding: 0;
}

.cw-bubble-assistant.cw-bubble--avatar > .cw-bubble-body,
.cw-bubble-assistant.cw-bubble-agent > .cw-bubble-body {
    background: var(--cw-bubble-assistant);
    border-radius: var(--cw-radius-lg);
    border-bottom-left-radius: 0.2rem;
    box-shadow: var(--cw-shadow-xs);
    padding: 0.6rem 0.9rem;
}

.cw-messages .cw-bubble-assistant.cw-bubble--avatar > .cw-bubble-body,
.cw-messages .cw-bubble-assistant.cw-bubble-agent > .cw-bubble-body {
    padding: var(--cw-bubble-padding);   /* A14-koukku, sama kuin paljaalla kuplalla */
}

.cw-bubble-who {
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--cw-text-muted);
    margin-bottom: 0.1rem;
}

.cw-bubble-avatar {
    flex: 0 0 auto;
    width: 1.4rem;
    height: 1.4rem;
    border-radius: 50%;
    overflow: hidden;
    background: var(--cw-primary);
    color: var(--cw-on-primary);
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1.4rem;
    text-align: center;
    /* Kuva voi olla mita tahansa mittasuhdetta — rajaus tehdaan tassa, ei
       luottaen siihen etta myyja lataa nelion. */
    display: block;
}

.cw-bubble-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Sama puhuja perakkain: paikanvaraaja pitaa kuplien vasemman reunan linjassa
   ilman etta avatar toistuu joka rivilla. */
.cw-bubble-avatar--spacer {
    background: transparent;
    visibility: hidden;
}

/* ===========================================================
 * KUPLA PUOLUSTAA OMAA TYPOGRAFIAANSA (aukko A46, 18.9.2026)
 *
 * OIRE: sama teksti näytti ERILAISELTA striimin aikana ja sen jälkeen.
 * Striimatessa kupla on paljas tekstisolmu (streamBubble.textContent) ja perii
 * kuplan oman `font-size`/`line-height`. `message_complete` korvaa sisällön
 * `div.innerHTML`illa, jossa on backendin sanitoimia lohkoelementtejä — ja NE
 * perivät ISÄNTÄSIVUN säännöt. Marginaalit ja fonttikoko ilmestyivät siis
 * kerralla striimin viimeisellä ruudulla. Demo joutui korjaamaan tämän omassa
 * `theme.css`:ssään, eli korjaus oli istutuksessa ja puuttui äidistä.
 *
 * KATE = SANITOIJAN ALLOWLIST, EI ARVAUS. Valitsinlista vastaa tasan
 * `chat_sanitize_profile_chat()`:n sallimaa joukkoa (sanitize.php:392–401):
 * p, strong, em, br, ul, ol, li, h1, h2, h3, a. Muut tagit eivät voi päätyä
 * kuplaan, joten niiden suojaaminen olisi kuollutta koodia — ja jos allowlist
 * joskus KASVAA, tämä lista on se joka on päivitettävä samalla.
 *
 * 🔴 MIKSI `.cw-messages`-ETULIITE EIKÄ PALJAS `.cw-bubble p`: sama perustelu
 * kuin aukossa A32 (ks. syöterivin fokuslohko ylempänä). `.cw-bubble p` on
 * spesifisyydeltään (0,1,1) — täsmälleen sama kuin isäntäteeman täysin
 * tavanomainen `.entry-content p`. Tasapeli ratkeaisi latausjärjestyksellä,
 * eli sääntö olisi ajossa inertti juuri niillä sivuilla joilla sitä tarvitaan.
 * `.cw-messages .cw-bubble p` = (0,2,1) voittaa ilman isäntäkohtaista
 * valitsinta.
 *
 * 🔴 MUTTA SPESIFISYYS EI RIITÄ ISÄNNÄN `!important`ia VASTAAN (aukko A380,
 * 22.9.2026). Yllä oleva perustelu on oikea tavallista isäntäsääntöä vastaan
 * ja PUUTTEELLINEN: `!important` voittaa spesifisyydestä riippumatta, joten
 * mikään valitsin ei auta sitä vastaan.
 *
 * MITATTU TUOTANNOSSA (PEL-tuote, Chrome): kuplan `<p>`:n laskettu
 * `margin-bottom` oli 25px eikä 0.5em, lähteenä isäntäteeman
 *     `.wp-block-heading, p { margin-bottom: 25px !important }`
 * (Digitaali-teema, `inc/gutenberg/css/common.css:150`). Coren sääntö hävisi
 * vaikka sen spesifisyys on korkeampi. Kuplassa oli siis kappaleilla 25px ja
 * listoilla 7.2px välit — tasan se epäsuhta josta Teemu valitti, ja aukon
 * A172 korjaus teki siitä NÄKYVÄMMÄN kutistaessaan listojen välin oikeaksi.
 *
 * KORJAUS ON `!important` COREN OMISSA KUPLASÄÄNNÖISSÄ. Perustelu: kahden
 * `!important`-julistuksen kesken ratkaisee taas SPESIFISYYS, joten coren
 * (0,2,1) voittaa isännän `p` (0,0,1) -säännön. Ennakkotapaus coressa:
 * `.cw-hidden` ja dokin piilotussääntö.
 *
 * 🔑 ISTUTUKSEN SÄÄTÖREITTI ON TOKEN, EI `!important` (K2-css-38, 23.9.2026).
 * Säädettävät arvot (kappaleväli, listan sisennys, otsikoiden koko/paino/
 * marginaali) ovat `var(--cw-bubble-*)`-tokeneita: `!important` koskee
 * deklaraatiota, arvo tulee kaskadista. Kerros 3:n `theme.css` yliajaa
 * tokenin `.cw-wrapper { --cw-bubble-p-gap: … }` -lohkossa kuten värit —
 * ei kilpailevaa `!important`-sääntöä. Tokenit: token-lohko tiedoston alussa.
 *
 * ⚠️ MIKSI EI `@layer` EIKÄ `:where()`: molemmat veisivät väärään suuntaan.
 * Kerroksettomat säännöt voittavat kerrostetut, joten coren siirto layeriin
 * tekisi siitä HEIKOMMAN; `:where()` taas nollaa spesifisyyden.
 *
 * 🔴 KATE ON MITATTU, EI ARVATTU. Skannasin molempien istutusten teemat
 * (peltuote + tumeagri) kaikkien `!important`-sääntöjen varalta joiden
 * valitsimen viimeinen osa on paljas tagi — kuplan sisältö on sanitoitua
 * LLM-HTML:ää ilman luokkia, joten vain sellaiset voivat osua siihen.
 * Osumia oli TASAN YKSI rajaamaton: `p { margin-bottom: 25px !important }`.
 * Loput (`.site-footer a`, `.dg-faq p`, `.dg-dokumentit-osio > h2`) ovat
 * luokkarajattuja eivätkä yllä kuplaan. Siksi `!important` on tässä
 * tiedostossa VAIN kuplan typografia- ja välistyssäännöissä — `strong`, `em`
 * ja `a` jäävät tietoisesti ilman, koska mittaus ei tue niitä.
 *
 * `inherit` EIKÄ literaaliarvo: kupla on jo asettanut oman asunsa, ja
 * periminen pitää mitat yhdessä paikassa (`.cw-bubble`). Literaali tekisi
 * tästä toisen totuuden joka ajautuisi erilleen. `inherit !important` on
 * turvallinen: se perii yhä kuplan OMAN arvon, ei isännän.
 * =========================================================== */

/* Tekstilohkot: koko asu kuplalta. `font-weight` on mukana koska teemat
   asettavat sen leipätekstille säännöllisesti (esim. `p { font-weight: 300 }`). */
.cw-messages .cw-bubble p,
.cw-messages .cw-bubble ul,
.cw-messages .cw-bubble ol,
.cw-messages .cw-bubble li {
    font-family: inherit !important;
    font-size: inherit !important;
    font-weight: inherit !important;
    font-style: inherit !important;
    line-height: inherit !important;
    color: inherit !important;
    text-align: inherit !important;
    letter-spacing: inherit !important;
    text-transform: none !important;
}

/* Kappaleiden välistys on COREN päätös, ei isännän. Reunojen nollaus on
   omassa lohkossaan alempana (`> *:last-child`) — EI täällä tagikohtaisesti,
   ks. aukko A168. */
.cw-messages .cw-bubble p {
    margin: 0 0 var(--cw-bubble-p-gap) !important;
    padding: 0 !important;
}

/* Listat: sisennys jotta pallurat eivat ole tekstin vasemmalla puolella.
   (Oli ennen `.cw-messages ul` = (0,1,1); kavennettu kuplaan ja nostettu
   samaan lohkoon, jottei samasta asiasta ole kahta totuutta. Widgetissa
   `.cw-messages`issa ei ole listoja kuplien ulkopuolella — tarkistettu:
   JS ei luo yhtaan ul/ol-elementtia, ne tulevat vain sanitoidusta HTML:sta.) */
/* 🔴 MARGINAALI ON SYMMETRINEN JA SAMA KUIN KAPPALEELLA (aukko A172,
   22.9.2026). Teemu: "ol ja vikan p valissa ei oo marginaalia... OL:lla pitais
   olla keskella sisaltoa yhta iso vali alapuolella kun ylapuolellakin."

   Arvo oli `0.3em 0`, ja naapurimarginaalien KOLLAPSOINTI teki siita
   epasymmetrisen kumpaankin suuntaan:
     p -> ol   max(0.5em, 0.3em) = 0.5em
     ol -> p   max(0.3em, 0)     = 0.3em   <- p:n margin-top on 0
   Lista sai siis ylapuolelleen enemman ilmaa kuin alapuolelleen. `0.5em 0`
   antaa kollapsoinnin jalkeen 0.5em molemmin puolin — eli listan valistys
   seuraa kappaleen valistysta itsestaan, eika kahta lukua tarvitse pitaa
   kasin synkassa. */
.cw-messages .cw-bubble ul,
.cw-messages .cw-bubble ol {
    padding-left: var(--cw-bubble-list-indent) !important;
    margin: var(--cw-bubble-p-gap) 0 !important;
    list-style-position: outside !important;
}

.cw-messages .cw-bubble li {
    margin-bottom: var(--cw-bubble-li-gap) !important;
    padding: 0 !important;
}

/* Otsikot: LLM tuottaa h1-h3:a myos lyhyeen vastaukseen, ja selaimen oletus
   (h1 = 2em + isot marginaalit) rikkoisi kuplan mittasuhteet. Skaala on
   maltillinen ja SUHTEELLINEN kuplan kokoon — ei absoluuttinen rem. */
.cw-messages .cw-bubble h1,
.cw-messages .cw-bubble h2,
.cw-messages .cw-bubble h3 {
    font-family: inherit !important;
    line-height: var(--cw-bubble-heading-line-height) !important;
    color: inherit !important;
    letter-spacing: inherit !important;
    text-transform: none !important;
    font-weight: var(--cw-bubble-heading-weight) !important;
    margin: var(--cw-bubble-heading-margin) !important;
    padding: 0 !important;
}

.cw-messages .cw-bubble h1 { font-size: var(--cw-bubble-h1-size) !important; }
.cw-messages .cw-bubble h2 { font-size: var(--cw-bubble-h2-size) !important; }
.cw-messages .cw-bubble h3 { font-size: var(--cw-bubble-h3-size) !important; }

/* ===========================================================
 * KUPLAN REUNAT: ENSIMMAISELTA YLA-, VIIMEISELTA ALAMARGINAALI POIS
 * (aukko A168, 22.9.2026)
 *
 * OIRE (Teemu): "Puhekuplan viimeinen <p> sisaltaa alamarginaalin, mika tekee
 * puhekuplaan turhaa tyhjaa loppuun ja nayttaa huonolta. Tama on kans aitiin
 * menevaa juttua, sama ongelma varmasti kaikissa."
 *
 * 🔴 MIKSI YKSI SAANTO EIKA TAGILISTA. Tassa OLI jo reset, mutta se kattoi
 * kolme tagia sanitoijan allowlistin yhdeksasta lohkotagista: `p`, `ul` ja
 * `ol`. Otsikoilla oli vain `:first-child`-puoli, eli viimeisena oleva h1-h3
 * jatti hannan — ja juuri tagilistan yllapito on se mekanismi jonka takia se
 * unohtui. `> *` ei voi unohtaa uutta tagia, joten allowlistin kasvaessa tata
 * ei tarvitse muistaa paivittaa.
 *
 * 🔴 MIKSI `> *:last-child` EIKA `p:last-child`. `:last-child` katsoo VANHEMMAN
 * viimeista lasta, ei viimeista kappaletta: yksikin `div`, `span` tai
 * sanitoinnin jattama elementti `p`:n jalkeen saa vanhan saannon lakkaamaan
 * toimimasta AANETTOMASTI. `:last-of-type` ei auta — se katsoo vain samaa
 * tagia. `> *` osuu aina siihen elementtiin joka on oikeasti viimeisena.
 *
 * 🔴 MIKSI KAKSI TASOA. Myyjan ja botin kuplassa (.cw-bubble-agent /
 * .cw-bubble--avatar, aukko A94) sisalto on kerrosta syvemmalla:
 *     .cw-bubble > .cw-bubble-avatar + .cw-bubble-body > (.cw-bubble-who + sisalto)
 * Pelkka `.cw-bubble > *:last-child` osuisi siis `.cw-bubble-body`yn eika sen
 * sisalla olevaan viimeiseen kappaleeseen — eli vika jaisi voimaan tasan
 * myyjan kuplassa. Kayttajan kuplassa kaaretta EI ole (chat-widget.js:348),
 * joten kumpikin taso on aidosti tarpeen.
 *
 * `.cw-bubble-who` on bodyn ensimmainen lapsi ja saa siis `margin-top: 0`:n.
 * Tarkistettu lahteesta: silla on vain `margin-bottom`, joten saanto on
 * sille no-op.
 *
 * SPESIFISYYS (0,2,1) + `>`: sama luokka kuin taman lohkon muilla saannoilla,
 * eli isantateeman taysin tavanomainen `.entry-content p { margin-bottom: 1em }`
 * (0,1,1) ei voita sita. Sama perustelu kuin aukossa A32. Tasapelin (esim.
 * `.cw-messages .cw-bubble p` ylempana) ratkaisee jarjestys, joten TAMAN LOHKON
 * ON PYSYTTAVA TYPOGRAFIASAANTOJEN JALKEEN.
 *
 * 🔴 `!important` TASSA ON PAKOLLINEN, EI TYYLISEIKKA (aukko A380). Isannan
 * `p { margin-bottom: 25px !important }` voittaa spesifisyydesta riippumatta,
 * joten ilman merkintaa tama saanto on inertti tasan siella missa sita
 * tarvitaan.
 *
 * ⚠️ JA TAMA OLI OSITTAIN RIKKI ILMAN ETTA SE NAKYI MITTAUKSESSA. Tuotannossa
 * mitattiin viimeisen lapsen `margin-bottom` = 0px ja paateltiin etta saanto
 * puree. Mittaus osui ULKOTASOON: `.cw-bubble > *:last-child` on myyjan ja
 * botin kuplassa `.cw-bubble-body` eli DIV, johon isannan `p`-saanto ei osu
 * — siella 0 voitti ilman kilpailua. SISATASO
 * (`.cw-bubble-body > *:last-child`) osuu oikeaan <p>:hen, ja se havisi
 * 25px:lle. Kaksi tasoa, kaksi eri lopputulosta, yksi mittauspiste.
 * =========================================================== */
.cw-messages .cw-bubble > *:last-child,
.cw-messages .cw-bubble-body > *:last-child {
    margin-bottom: 0 !important;
}

.cw-messages .cw-bubble > *:first-child,
.cw-messages .cw-bubble-body > *:first-child {
    margin-top: 0 !important;
}

/* Korostukset: asu kuplalta, mutta OMA paino/kursiivi sailyy — juuri se on
   naiden tagien koko merkitys. */
.cw-messages .cw-bubble strong {
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    color: inherit;
    font-weight: 700;
}

.cw-messages .cw-bubble em {
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    color: inherit;
    font-style: italic;
}

/* Linkit: VARI PERITAAN KUPLALTA. Isannan brandivari on valittu sivun taustaa
   vasten, ei kayttajakuplan taytevaria vasten — se voi jaada lukukelvottomaksi
   juuri siella. Alleviivaus jaa erottelevaksi keinoksi (se ei ole varista
   riippuvainen, WCAG 1.4.1). */
.cw-messages .cw-bubble a {
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    color: inherit;
    text-decoration: underline;
}

/* ===========================================================
 * KAYTTAJAN OMAN KUPLAN KOLLAPSOINTI (aukko A56, 18.9.2026)
 *
 * Pitka oma viesti nakyy kutistettuna + feidattuna, ja avausnappi laajentaa.
 * Malli: Grok, claude.ai. Uusi ominaisuus — ei pura mitaan (0 osumaa coresta
 * ja kaikista neljasta istutuksesta kirjoitushetkella).
 *
 * MIKSI VAIN KAYTTAJAN KUPLA: kavija tietaa mita itse kirjoitti, joten oma
 * pitka viesti on kontekstia jonka saa piilottaa. Vastaus on se jota luetaan.
 *
 * 🔴 `box-sizing: content-box` ON EKSPLISIITTINEN, EI OLETUS. `max-height`
 * laskee tasmalleen N rivia tekstia, mika pitaa paikkansa VAIN content-boxissa.
 * Tiedostossa ei ole globaalia `*`-resettia, mutta isantateemoissa on lahes
 * aina (`* { box-sizing: border-box }` on Bootstrapin ja kaikkien modernien
 * teemojen vakio) — ja se periytyisi tanne. Border-boxissa sama calc jattaisi
 * kuplan pystypaddingin verran vahemman tekstia nakyviin eli klippaisi KESKELTA
 * RIVIA. Sama aukon A46 oppi: kupla ei saa luottaa isannan laatikkomalliin.
 *
 * RIVIMAARA ON TOKENI: oletus 2 eika 1. Spec sanoo "~1 rivi + feidaus", mutta
 * yhden rivin klippaus feidauksen alla tekisi ainoasta nakyvasta rivista
 * puoliksi lukukelvottoman. Kahdella ensimmainen rivi on luettava ja toinen
 * feidautuu. Arvon muuttaminen on yhden tokenin tyo.
 * =========================================================== */
.cw-bubble-user.cw-bubble--collapsed {
    position: relative;
    overflow: hidden;
    box-sizing: content-box;
    max-height: calc(var(--cw-bubble-collapse-lines) * var(--cw-bubble-line-height) * 1em);

    /* 🔴 ALA POISTA TATA RIVIA. Se ei ole asettelun hienosaatoa vaan syy siihen,
       etta `max-height` ylla on ylipaansa sitova (aukko A166, 22.9.2026).

       OIRE: kutistettu kupla nakyi TUOTANNOSSA pelkkana viivana — ei kahtena
       rivina. Mitattu Chromella oikeasta kuplasta: `clientHeight` 14px kun
       `max-height` oli 44.64px ja padding 7.2+7.2 = sisaltokorkeus NOLLA.

       MEKANISMI: `overflow: hidden` poistaa flex-itemilta automaattisen
       minimikoon — `min-height: auto` resolvoituu nollaan sisaltokorkeuden
       sijaan. `.cw-messages` on ylivuotava sarakesuuntainen flex-kontti
       (clientHeight 333 vs scrollHeight 3624), joten oletus `flex-shrink: 1`
       kutistaa kuplan niin pitkalle kuin paasee — `max-height`in ALI.
       Kolmen pisteen mittaus: ilman luokkaa offsetHeight 37, luokan kanssa 14,
       luokan kanssa + flex-shrink:0 takaisin 37.

       MIKSI VAIN KOLLAPSOIDUSSA SAANNOSSA eika `.cw-messages > *`:ssa:
       `overflow: hidden` on `.cw-messages`in flex-lapsista vain tassa
       (tarkistettu: .cw-panel-title, .cw-conversation-title ja
       .cw-bubble-avatar ovat syvemmalla eivatka ole sen lapsia). Laajemman
       saannon blast radius olisi koko viestivirta. */
    flex-shrink: 0;
}

/* Feidaus, EI ellipsis. `-webkit-line-clamp` olisi antanut "…"-merkin, mutta
   Teemun pyynto on liukuva haipyminen — ja line-clamp olisi vaatinut
   `display: -webkit-box`in, joka muuttaisi kuplan sisaisen layoutin.
   Gradientti haipyy kuplan omaan taytevariin, joten se toimii myos
   brandivaihdetulla tokenilla. */
.cw-bubble-user.cw-bubble--collapsed::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: calc(var(--cw-bubble-line-height) * 1em);
    background: linear-gradient(to bottom, transparent, var(--cw-bubble-user));
    pointer-events: none;
}

/* Avausnappi on kuplan SISAR, ei sen lapsi: kupla klippaa `overflow: hidden`illa,
   joten sisalla oleva nappi katoaisi juuri kutistetussa tilassa. Sisar myos
   pitaa `state.lastUserBubble`in osoittamassa samaan `.cw-bubble`-diviin, jota
   kolme muuta mekanismia lukee (rollback, A43-ankkurointi, alatilan tayte). */
/* IKONINAPPI, EI TEKSTILINKKI (aukko A167, 22.9.2026). Teemu: "'Nayta koko
   viesti' -teksti vittuun, tilalle joku puhekuplan alareunan ikoni".

   🔴 KOSKETUSKOHDE >= 24x24 CSS-PIKSELIA vaikka ikoni itse on pienempi
   (WCAG 2.2, 2.5.8 Target Size Minimum). Tekstinapin `padding: 0.1rem 0.3rem`
   ei riittanyt siihen; min-width/min-height pitaa alueen riittavana myos jos
   ikonin kokoa joskus pienennetaan.

   Napin saavutettava nimi on `aria-label` (collapseSetExpanded) — tassa ei
   ole nakyvaa tekstia, joten `font-size`/`text-decoration` eivat enaa kuulu
   tanne. `font-family: inherit` jai pois samasta syysta. */
.cw-bubble-toggle {
    align-self: flex-end;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 24px;
    min-height: 24px;
    padding: 0;
    margin: -0.2rem 0 0;
    background: none;
    border: none;
    line-height: 0;
    color: var(--cw-text-muted);
    cursor: pointer;
}

/* Ikonin VARI periytyy napilta `currentColor`in kautta (svg stroke) — siksi
   hover riittaa vaihtamaan napin varin, ei ikonin omaa saantoa. */
.cw-bubble-toggle-icon {
    width: 1rem;
    height: 1rem;
    display: block;
}

.cw-bubble-toggle:hover {
    color: var(--cw-text);
}

.cw-bubble-toggle:focus-visible {
    box-shadow: var(--cw-focus-ring);
    border-radius: var(--cw-radius);
    outline: none;
}

/* ===========================================================
 * TERVETULOVIESTI
 * =========================================================== */
.cw-welcome {
    text-align: center;
    padding: 1rem 0.5rem;
    color: var(--cw-text-muted);
}

/* Tyhja tervehdys ei saa varata tilaa (aukko A373). Ilman tata istutus jolla
 * ei ole tervehdysta sai 2rem pystypaddingia viestialueen ylareunaan — tyhjan
 * nakoinen rako jolle ei ole mitaan selitysta. Sama kuvio kuin
 * .cw-disclosure:empty. Portti on JS:ssa (hasWelcomeContent), tama on sen
 * visuaalinen pari. */
.cw-welcome:empty {
    display: none;
}

/* A376/A384: iso hahmo TERVEHDYKSESSA (vain myyjatoiminto paalla, task 403).
 * Osa tervehdysta — se rullautuu pois yhdessa tekstin kanssa kun kavija
 * lahettaa ensimmaisen viestin.
 *
 * MUOTO (Teemu 22.9.2026): hahmo seisoo kuplan VIERESSA, ei kuplassa.
 * `.cw-welcome--avatar` on flex-rivi, hahmo vasemmalla, ja kuplatausta on vain
 * tekstikaareella `.cw-welcome-bubble`. Modifier ja kaare syntyvat JS:sta vain
 * kun hahmo on — ilman hahmoa `.cw-welcome` on ennallaan (keskitetty teksti).
 *
 * MITAT REMEINA JA max-*:na, EI kiinteana leveys+korkeus-parina: hahmokuvan
 * kuvasuhdetta ei tiedeta etukateen (istutus toimittaa sen), ja kiinteat mitat
 * venyttaisivat sen. Istutus saataa kokoa omalla theme.css:llaan. */
.cw-welcome-avatar {
    display: block;
    margin: 0 auto 0.6rem;
    max-width: 8rem;
    max-height: 8rem;
    object-fit: contain;
}

.cw-welcome--avatar {
    display: flex;
    align-items: flex-end;
    gap: 0.75rem;
    text-align: start;
    padding: 0.5rem 0;
}

.cw-welcome--avatar .cw-welcome-avatar {
    flex: 0 0 auto;
    margin: 0;
}

/* Tekstikaare kayttaa botin kuplan tokeneita: tervehdys ON botin ensimmainen
 * repliikki, ja omat tokenit tekisivat siita kolmannen kuplatyylin. */
.cw-welcome-bubble {
    min-width: 0;
    background: var(--cw-bubble-assistant);
    color: var(--cw-bubble-assistant-text);
    border-radius: var(--cw-radius-lg);
    border-bottom-left-radius: 0.2rem;
    box-shadow: var(--cw-shadow-xs);
    padding: 0.6rem 0.9rem;
}

/* ===========================================================
 * NAYTTAMOHAHMO (task 403): iso hahmo PYSYVASTI viestialueen reunassa kun
 * myyjatoiminto on pois. `.cw-stage` on rivi [hahmo | viestialue], ja VAIN
 * viestialue vierii — siksi hahmo ei rullaudu. Syntyy JS:sta vain kun hahmo
 * on; ilman sita paneelin puu on ennallaan eika mikaan nama saannot osu.
 *
 * Mitat samalla periaatteella kuin tervehdyksen hahmolla (max-*, ei kiinteaa
 * paria). Istutus saataa kokoa theme.css:llaan (PEL: ~205 px korkea).
 * =========================================================== */
.cw-stage {
    display: flex;
    align-items: stretch;
    flex: 1 1 auto;
    /* 🔴 min-height: 0 — sama syy kuin upotetun paneelin kohdalla: ilman sita
     * flex-lapsi ei kutistu sisaltoaan pienemmaksi ja `.cw-messages`in
     * `overflow-y: auto` ei aktivoidu koskaan. */
    min-height: 0;
}

.cw-stage > .cw-messages {
    min-width: 0;
}

.cw-stage-avatar {
    flex: 0 0 auto;
    align-self: flex-end;
    max-width: var(--cw-stage-avatar-width);
    max-height: var(--cw-stage-avatar-max-height);
    margin: 0 0 0.3rem 0.75rem;
    object-fit: contain;
    pointer-events: none;
}

/* HAHMO EI NOUSE OTSIKKOPALKIN PAALLE (task 419, A5).
 *
 * Kiintea `max-height: 12rem` flex-rivissa: kun paneeli on matala (ruutu
 * matala tai --cw-panel-max-height pieni), `.cw-stage` kutistuu
 * (min-height: 0) mutta hahmo ei, ja se nousi otsikkopalkin paalle.
 * Prosenttiraja flex-lapsessa ei ratkea, koska paneelilla on vain
 * max-height (mitattu PEL 22.9.: hahmo kasvoi luonnolliseen 204 px:iinsa).
 * Absoluuttisena prosentti lasketaan AINA `.cw-stage`n korkeudesta: hahmo
 * kutistuu tilan mukana ja on taysikokoinen vasta kun tilaa on. Viestien
 * paikka varataan marginaalilla (hahmon leveystoken + sama 0.75rem kuin
 * hahmon reunavali). Logical-ominaisuudet: RTL peilautuu itsestaan.
 *
 * ⚠️ Seuraus: hahmo EI enaa kasvata paneelia. Tyhjassa fokustilassa (vain
 * tervehdys) matala paneeli nayttaa pienen hahmon — istutus antaa
 * viestialueelle min-korkeuden jos haluaa isomman (PEL: 200 px).
 * Alle 576 px hahmo on piilossa (MOBIILI-lohko), joten saannot vain sen yli. */
@media (min-width: 576px) {
    .cw-stage {
        position: relative;
    }

    .cw-stage > .cw-messages {
        margin-inline-start: calc(var(--cw-stage-avatar-width) + 0.75rem);
    }

    .cw-stage-avatar {
        position: absolute;
        inset-inline-start: 0;
        bottom: 0.3rem;
        margin: 0;
        margin-inline-start: 0.75rem;
        max-height: min(var(--cw-stage-avatar-max-height), calc(100% - 0.6rem));
        width: auto;
    }
}

.cw-welcome-title {
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--cw-text);
    margin-bottom: 0.3rem;
}

.cw-welcome-text {
    font-size: 0.8rem;
    line-height: 1.6;
}

/* ===========================================================
 * AI-DISCLOSURE paneelin pohjalla (EU AI Act art. 50)
 * Peilaa .gc-disclosurea.
 * =========================================================== */
.cw-disclosure {
    flex-shrink: 0;   /* A12: ei kutistu matalassa paneelissa */
    padding: 0.35rem 0.75rem;
    font-size: 0.7rem;
    line-height: 1.3;
    color: var(--cw-text-muted);
    border-top: 1px solid var(--cw-border);
}

/* Tyhja config → ei nakyva palkkia (backward-compat vanhoille istutuksille) */
.cw-disclosure:empty {
    display: none;
}

/* ===========================================================
 * VASTUUVARAUMA JA SELOSTE (A110, A494) — YKSI RIVI (A702, 1.10.2026)
 * Kun varauma tai selosteen osoite on asetettu, disclosuren teksti on itse
 * avaaja (.cw-disclosure__trigger): <a> selosteeseen tai <button> joka avaa
 * .cw-disclosure__detail-nakyman (varauma + selostelinkki). Rakenne:
 *   .cw-disclosure-row > .cw-disclosure__trigger > .cw-disclosure (+ varateksti)
 * Avaaja on linkin nakoinen, koska se ON rivin lause — margin/background
 * nollataan isannan button-saantoja vastaan (vrt. task 418).
 * =========================================================== */
.cw-disclosure__trigger {
    display: inline;
    margin: 0;
    padding: 0;
    background: none;
    border: none;
    font: inherit;
    line-height: inherit;
    color: inherit;
    text-align: inherit;
    text-decoration: underline;
    text-underline-offset: 0.15em;
    cursor: pointer;
}

.cw-disclosure__trigger:focus-visible {
    box-shadow: var(--cw-focus-ring);
}

/* Disclosure avaajan sisalla on inline-teksti, ei oma palkkinsa. */
.cw-disclosure__trigger > .cw-disclosure {
    display: inline;
    padding: 0;
    border-top: 0;
}

/* Human-tila ilman selostetta (A702-jatko, paatos #757 a): avaaja irrotetaan
 * ja disclosure on rivin suora lapsi, pelkka teksti. Rivi kantaa jo rajan ja
 * paddingin (:has-saanto alla), joten disclosuren omat nollataan. */
.cw-disclosure-row > .cw-disclosure {
    display: inline;
    padding: 0;
    border-top: 0;
}

.cw-disclosure__trigger > .cw-disclosure:empty {
    display: none;
}

/* Varateksti vain kun disclosure on tyhja: nakyvissa aina tasan yksi teksti. */
.cw-disclosure__trigger > .cw-disclosure:not(:empty) + .cw-disclosure__fallback {
    display: none;
}

/* Linkin saavutettavan nimen jatke ("Tietosuojaseloste (avautuu uuteen
 * valilehteen)") — ruudunlukijalle, ei nakyviin. */
.cw-disclosure__sr {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.cw-disclosure__detail {
    flex-shrink: 0;
    padding: 0 0.75rem 0.35rem;
    font-size: 0.7rem;
    line-height: 1.3;
    color: var(--cw-text-muted);
    white-space: pre-line;   /* textarea-optio: rivinvaihdot sailyvat */
}

/* ===========================================================
 * AVAUSNAPPI (A524, task 470) — vain kun cfg.launcher on paalla ja dokissa.
 * Wrapperin data-cw-launcher="1" on portti: ilman sita nama saannot eivat
 * osu mihinkaan eika dokin alapalkki muutu. Suljettuna syoterivi piilossa ja
 * nappi nakyvissa; auki (.cw-active) painvastoin. Asemointi ja koko ovat
 * istutuksen teeman asia (kerros 3), tassa vain toimiva oletus oikeaan reunaan.
 * =========================================================== */
.cw-wrapper[data-cw-launcher="1"]:not(.cw-active) .cw-input-row {
    display: none;
}

.cw-launcher {
    display: block;
    margin: 0 0.75rem 0.75rem auto;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--cw-border);
    border-radius: 999px;
    background: var(--cw-send-bg);
    color: #fff;
    font: inherit;
    cursor: pointer;
}

.cw-launcher:focus-visible {
    box-shadow: var(--cw-focus-ring);
}

.cw-launcher__image {
    display: block;
    width: 96px;
    height: auto;
}

.cw-wrapper.cw-active .cw-launcher {
    display: none;
}

/* ===========================================================
 * LLM-GUIDED-PUTKI (task 470): fallback-vastaus ja "Muokkaa vastausta".
 * Fallback = ennalta hyvaksytty "en loytanyt vastausta" -teksti; oletuksena
 * vain hillitty ero, istutuksen teema voi korostaa.
 * =========================================================== */
.cw-bubble--fallback {
    font-style: italic;
}

.cw-msg-edit {
    display: block;
    margin-top: 0.4rem;
    font-size: 0.75rem;
    color: var(--cw-text-muted);
    text-decoration: underline;
}

/* Disclosure-rivi avaajan kanssa (A559 -> A702). Kaare syntyy VAIN kun
 * varauma tai seloste on asetettu, joten istutuksen DOM ilman niita ei muutu.
 * Istutuksen teema voi siirtaa rivin paneelin ylaosaan `order`illa —
 * kohde on .cw-disclosure-row (paneelin flex-lapsi). */
.cw-disclosure-row {
    flex-shrink: 0;   /* A12: ei kutistu matalassa paneelissa */
    padding: 0 0.75rem 0.35rem;
    font-size: 0.7rem;
    line-height: 1.3;
    color: var(--cw-text-muted);
}

/* Disclosuren raja ja ylapadding siirtyvat kaareelle vain kun disclosurella
 * on tekstia; pelkka varateksti nayttaa kuten ennen A559:aa (ei rajaa). */
.cw-disclosure-row:has(.cw-disclosure:not(:empty)) {
    padding-top: 0.35rem;
    border-top: 1px solid var(--cw-border);
}

/* Selostelinkki varauman nakymassa (A494/A702): varauman alla omalla rivillaan. */
.cw-disclosure__privacy {
    color: inherit;
    text-decoration: underline;
    white-space: nowrap;
}

.cw-disclosure__privacy:focus-visible {
    box-shadow: var(--cw-focus-ring);
}

.cw-disclosure__disclaimer {
    display: block;
}

.cw-disclosure__disclaimer + .cw-disclosure__privacy {
    display: inline-block;
    margin-top: 0.2rem;
}

/* display: block yllä voittaisi UA:n [hidden]-saannon (human-tila piilottaa). */
.cw-disclosure__disclaimer[hidden],
.cw-disclosure__detail[hidden] {
    display: none;
}

/* ===========================================================
 * YHTEYSRIVI (takeover-poll, task 215a)
 * Nakyy vasta kolmannen perakkaisen poll-virheen jalkeen. Kevyt tekstirivi,
 * ei modaalia: modaali jaadyttaisi sivun ja tekisi ominaisuudesta testaamattoman
 * (pakolliset.md § Selaimen dialogit).
 * =========================================================== */
.cw-connection {
    flex-shrink: 0;   /* A12 */
    padding: 0.35rem 0.75rem;
    font-size: 0.7rem;
    line-height: 1.3;
    /* Olemassa oleva token, ei uutta: yhteyskatko on virhetila mutta rivi on
       tarkoituksella hillitty (ei paniikkia, viesti tulee viiveella). */
    color: var(--cw-error);
    border-top: 1px solid var(--cw-border);
}

.cw-connection:empty {
    display: none;
}

/* ===========================================================
 * MYYJAKORTTI (takeover, task 215b)
 *
 * Paneelin ylaosassa, header-rivin alla. Pinnattu koko haltuunoton ajaksi:
 * "kuka minua palvelee" on kysymys johon asiakas palaa kesken keskustelun.
 *
 * 🔴 Kortti TYHJENNETAAN purussa (renderAgentCard: textContent = ''), ei vain
 * piiloteta — [hidden] on tassa toinen kerros, ei ainoa. Puhelinnumero ei saa
 * jaada DOM-puuhun luettavaksi sen jalkeen kun myyja on luovuttanut.
 * =========================================================== */
.cw-agent-card {
    flex-shrink: 0;   /* A12 */
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--cw-border);
    background: var(--cw-surface);
}

.cw-agent-card[hidden] {
    display: none;
}

.cw-agent-card__head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.cw-agent-card__id {
    display: flex;
    flex-direction: column;
    min-width: 0;   /* pitkat nimet katkeavat, eivat levita korttia */
    /* Vie jaljelle jaava tila -> Yhteystiedot-nappi pysyy oikeassa reunassa. */
    flex: 1 1 auto;
}

.cw-agent-card__name {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--cw-text);
}

.cw-agent-card__title {
    font-size: 0.7rem;
    color: var(--cw-text-muted);
}

.cw-agent-card__toggle {
    flex: 0 0 auto;
    background: none;
    border: 1px solid var(--cw-border);
    border-radius: var(--cw-radius);
    color: var(--cw-text-muted);
    font-size: 0.7rem;
    padding: 0.2rem 0.5rem;
    cursor: pointer;
}

.cw-agent-card__toggle:hover {
    color: var(--cw-text);
}

.cw-agent-card__details {
    margin-top: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.cw-agent-card__details[hidden] {
    display: none;
}

.cw-agent-card__row {
    display: flex;
    gap: 0.4rem;
    font-size: 0.75rem;
}

.cw-agent-card__label {
    color: var(--cw-text-muted);
    flex: 0 0 4.5rem;
}

.cw-agent-card__link {
    color: var(--cw-primary);
    text-decoration: none;
    word-break: break-all;
}

.cw-agent-card__link:hover {
    text-decoration: underline;
}

.cw-agent-card__value {
    color: var(--cw-text);
    word-break: break-all;
}

/* ===========================================================
 * SIIRTOTARJOUSPALKKI (takeover 215b, A470 / task 455)
 *
 * KIINNITETTY syoterivin ylapuolelle (.cw-input-row > .cw-handoff ennen
 * .cw-input-inner), EI viestivirtaan: virrassa tarjous valui ylos pois
 * nakyvista heti kun keskustelu jatkui (Teemun paatos 9.9.2026). Sama paikka
 * ja sama perhe kuin elepalkilla — ohut, koko rivin levyinen.
 * EI modaali (pakolliset.md § Selaimen dialogit), EI peita syotetta: palkki
 * on syoterivin lapsi, joten se kasvattaa rivia ylospain eika voi menna
 * kentan paalle. Mobiilissa max-height + vieritys, jotta auki oleva
 * nappaimisto ei tyonna kenttaa pois nakyvista.
 * Istutus yliajaa tokeneilla (--cw-handoff-*), ei tata tiedostoa muokkaamalla.
 * =========================================================== */
.cw-handoff {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.6rem;
    margin: 0 0 var(--cw-ele-gap, 0.25rem);
    padding: 0.4rem 0.5rem 0.4rem 0.6rem;
    box-sizing: border-box;
    width: 100%;
    max-height: var(--cw-handoff-max-height, 8rem);
    overflow-y: auto;
    background: var(--cw-handoff-bg, var(--cw-surface));
    color: var(--cw-handoff-text, var(--cw-text));
    border: 1px solid var(--cw-handoff-border, var(--cw-primary));
    border-radius: var(--cw-handoff-radius, var(--cw-radius-lg));
    font-size: var(--cw-handoff-font-size, 0.85rem);
    line-height: 1.3;
}
/* Sama saanto kuin elepalkilla (A16): kiinni olevan chatin syoterivin paalla
 * palkki kelluisi sivun paalla. Palaa kun chat avataan. Upotettu moodi ei
 * koskaan saa .cw-active-luokkaa, siksi rajaus. */
.cw-wrapper:not(.cw-active):not([data-cw-mode="embed"]) .cw-handoff {
    display: none;
}

.cw-handoff__who {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: 1 1 12rem;
    min-width: 0;
}

.cw-handoff__id {
    display: flex;
    flex-wrap: wrap;
    column-gap: 0.3rem;
    min-width: 0;   /* pitka nimi katkeaa, ei levita palkkia */
}

.cw-handoff__name {
    font-weight: 600;
}

.cw-handoff__title {
    color: var(--cw-text-muted);
}

.cw-handoff__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    flex: 0 0 auto;
}
/* Mobiili: ks. MOBIILI-lohko (.cw-actionbarin vieressa). Ei omaa @media-
 * lohkoa tassa: test-widget-embed.js lukee ensimmaisen 575.98px-lohkon
 * mobiilisaannoiksi, ja kaksi lohkoa samalle rajalle hajauttaa saman asian. */

.cw-handoff__btn {
    background: var(--cw-surface);
    border: 1px solid var(--cw-border);
    border-radius: var(--cw-radius-lg);
    color: var(--cw-text);
    font-size: 0.8rem;
    padding: 0.4rem 0.8rem;
    cursor: pointer;
}

.cw-handoff__btn:hover {
    border-color: var(--cw-primary);
}

.cw-handoff__btn--yes {
    background: var(--cw-primary);
    border-color: var(--cw-primary);
    color: var(--cw-on-primary);
}

/* ===========================================================
 * MYYJAN KIRJOITUSMERKKI (takeover, task 215b)
 *
 * 🔴 OMA NIMIPERHE (.cw-agent-typing), EI .cw-typing. Jalkimmainen on AI:n
 * PYSYVA indikaattorielementti jolla on oma nakyvyyslogiikka (cw-hidden) —
 * nimen jakaminen olisi hiljainen vika: myyjan merkki perisi tyylit ja
 * piilotuksen jotka on kirjoitettu eri elementille.
 * =========================================================== */
.cw-agent-typing__dots {
    display: flex;
    gap: var(--cw-typing-gap);
    /* Oma padding, EI --cw-typing-padding: nama pisteet ovat jo kuplan sisalla
     * (ks. tokenilohkon A45-kommentti). Kuplatokenit koskevat .cw-typingia. */
    padding: 0.15rem 0;
}

.cw-agent-typing__dot {
    display: var(--cw-typing-dot-display);
    width: var(--cw-typing-dot-size);
    height: var(--cw-typing-dot-size);
    background: var(--cw-typing-dot-color);
    border-radius: 50%;
    animation: var(--cw-typing-animation);
}

.cw-agent-typing__dot:nth-child(2) {
    animation-delay: var(--cw-typing-delay-2);
}

.cw-agent-typing__dot:nth-child(3) {
    animation-delay: var(--cw-typing-delay-3);
}

/* ===========================================================
 * VASTAUKSEN LATAUSANIMAATIO TOOL-KIERROKSEN JALKEEN (aukko A393, task 408)
 *
 * Kolme pistetta vastauskuplan SISALLA valilla tool_result → ensimmainen
 * text_delta (chat-widget.js addStreamLoader). Oma luokka, koska .cw-typing ja
 * .cw-agent-typing ovat eri elementteja omalla nakyvyyslogiikallaan (ks.
 * ylla); pistetyylit ja tokenit ovat samat kuin myyjan merkissa — kupla
 * ymparilta on jo olemassa, joten kuplatokeneita ei kayteta.
 * =========================================================== */
.cw-stream-loader {
    display: flex;
    gap: var(--cw-typing-gap);
    padding: 0.15rem 0;
}

.cw-stream-loader span {
    display: var(--cw-typing-dot-display);
    width: var(--cw-typing-dot-size);
    height: var(--cw-typing-dot-size);
    background: var(--cw-typing-dot-color);
    border-radius: 50%;
    animation: var(--cw-typing-animation);
}

.cw-stream-loader span:nth-child(2) {
    animation-delay: var(--cw-typing-delay-2);
}

.cw-stream-loader span:nth-child(3) {
    animation-delay: var(--cw-typing-delay-3);
}

/* ===========================================================
 * TYPING-INDIKAATTORI
 * =========================================================== */
.cw-typing {
    align-self: flex-start;
    display: flex;
    gap: var(--cw-typing-gap);
    padding: var(--cw-typing-padding);
    background: var(--cw-typing-bg);
    border-radius: var(--cw-typing-radius);
    border-bottom-left-radius: 0.2rem;
    box-shadow: var(--cw-typing-shadow);
}

/* Hahmokuva (A45): oletuksena display:none, joten tama pseudoelementti ei
 * renderoidu eika vaikuta mihinkaan ennen kuin istutus kytkee sen.
 * VAIN AI:n indikaattorissa — myyjan kirjoitusmerkki on ihminen eika
 * chat-hahmo, ja hahmokuva siina vaittaisi kayttajalle vaaraa asiaa siita
 * kuka kirjoittaa. */
.cw-typing::before {
    content: '';
    display: var(--cw-typing-media-display);
    flex: none;
    width: var(--cw-typing-media-size);
    height: var(--cw-typing-media-size);
    background-image: var(--cw-typing-media);
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
}

.cw-typing span {
    display: var(--cw-typing-dot-display);
    width: var(--cw-typing-dot-size);
    height: var(--cw-typing-dot-size);
    background: var(--cw-typing-dot-color);
    border-radius: 50%;
    animation: var(--cw-typing-animation);
}

.cw-typing span:nth-child(2) {
    animation-delay: var(--cw-typing-delay-2);
}

.cw-typing span:nth-child(3) {
    animation-delay: var(--cw-typing-delay-3);
}

/* Prefiksoitu keyframe: geneerinen `bounce` tormaa isantasivun animaatioihin
   (Bootstrap, Animate.css, useat WP-teemat maarittelevat sen). */
@keyframes cw-bounce {
    0%, 60%, 100% { transform: translateY(0); }
    30% { transform: translateY(-0.35rem); }
}

/* ===========================================================
 * TOOL CALL STATUS (streaming) — nakyy kuplassa kun tyokalu suoritetaan
 * =========================================================== */
.cw-tool-status {
    display: flex;
    align-items: center;
    gap: var(--cw-tool-status-gap);
    /* Kuplan kokoa ja tyylia (A6): 0.85rem + kursiivi teki rivista kuplan
     * muuta tekstia isomman ja eri nakoisen. Vari jaa hillityksi. */
    font-size: inherit;
    color: var(--cw-text-muted);
    font-style: normal;
    padding: 4px 0;
}

/* Elava indikaattori tekstin edessa (korvasi '\23F3 '-tiimalasin 23.9.2026).
 * Kaikki arvot tokeneista — istutus vaihtaa renkaan hahmokuvaan kerros 3:ssa,
 * ks. :root § Tyokalun tilarivi. Tausta on oletuksena `none`, joten rengas
 * on se mika nakyy; kun istutus antaa kuvan ja nollaa renkaan, jaljelle jaa kuva. */
.cw-tool-status::before {
    content: '';
    flex: none;
    display: var(--cw-tool-status-indicator-display);
    box-sizing: border-box;
    width: var(--cw-tool-status-indicator-size);
    height: var(--cw-tool-status-indicator-size);
    border: var(--cw-tool-status-ring-width) solid var(--cw-tool-status-ring-color);
    border-top-color: var(--cw-tool-status-ring-accent);
    border-radius: var(--cw-tool-status-indicator-radius);
    background-image: var(--cw-tool-status-media);
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    animation: var(--cw-tool-status-animation);
}

/* Ruudunlukijan pysyva teksti. Nakyva teksti vaihtuu muutaman sekunnin
 * valein ja on aria-hidden, koska .cw-messages on aria-live-alue: jokainen
 * vaihto luettaisiin muuten aaneen. Tama kopio ilmoitetaan kerran, kun
 * tilarivi lisataan. */
.cw-tool-status .cw-tool-status-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

@keyframes cw-spin {
    to { transform: rotate(360deg); }
}

/* ===========================================================
 * TOOL-TULOSKORTTI + KNOWLEDGE-LISTA (aukko A163, 22.9.2026)
 *
 * Renderoija (chat-widget.js renderKnowledgeList) on luonut nama viisi luokkaa
 * alusta asti, eika yhdellakaan ollut CSS:aa — ei aidissa eika yhdessakaan
 * viidesta istutuksesta. Kortti nakyi kavijalle rikkinaisena.
 *
 * 🔴 KONTTI TARVITSEE SAANNON YHTA KIPEASTI KUIN SISALTO. `.cw-tool-result`
 * insertoidaan SUORAAN `.cw-messages`iin (renderToolCall), EI kuplan sisaan,
 * eika `.cw-messages` aseta `font-size`ia lainkaan — kortti siis peri
 * ISANTASIVUN fonttikoon kun kuplat ovat 0.8rem. Se oli koko "jattifontin"
 * mekanismi, eika pelkkien sisaluokkien tyylittely olisi korjannut sita.
 *
 * 🔴 `.cw-messages`-ETULIITE EIKA PALJAS `.cw-knowledge-list`: sama perustelu
 * kuin aukossa A32 ja kuplatypografialohkossa ylempana. `.cw-knowledge-list`
 * olisi (0,1,0) eli tasapelissa isantateeman taysin tavanomaisen `ul`- tai
 * `.entry-content ul` -saannon kanssa, ja tasapeli ratkeaisi latausjarjestyksella
 * — saanto olisi inertti tasan niilla sivuilla joilla sita tarvitaan.
 * `.cw-messages .cw-knowledge-list` = (0,2,0) voittaa ilman `!important`ia.
 *
 * 🔴 `.cw-tool-result` ON JAETTU LAAJENNUSPINTA, ei vain knowledgen: kerros 3:n
 * omat renderoijat saavat saman kontin. Siksi tassa on tarkoituksella VAIN
 * mitat ja perityt arvot — ei vareja, ei taustaa, ei reunoja, ei brandia.
 * Ulkoasu on kerros 3:n osuus, rakenne coren.
 * =========================================================== */
.cw-messages .cw-tool-result {
    /* .cw-messages on flex-sarake ja kortti sen SUORA lapsi: ilman naita se
       venyisi koko leveydelle ja asettuisi eri linjaan kuin vastauskuplat. */
    align-self: flex-start;
    max-width: 85%;
    /* Sama luku kuin `.cw-messages .cw-bubble`illa: kortti on widgetin sisaista
       sisaltoa, ei isantasivun tekstia. */
    font-size: 0.8rem;
    line-height: var(--cw-bubble-line-height);
    color: var(--cw-text);
}

/* Ei marginaaleja: `.cw-messages`in `gap` hoitaa korttien valit. */
.cw-messages .cw-knowledge-list,
.cw-overlay .cw-knowledge-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* list-style myos itemille (task 519): isanta voi asettaa sen suoraan li:lle
   (Pico `ul li { list-style: square }`), jolloin listan `none` ei periydy ja
   ▪ piirtyy overlayn reunaan sisallon ulkopuolelle. */
.cw-messages .cw-knowledge-item,
.cw-overlay .cw-knowledge-item {
    list-style: none;
    margin: 0 0 0.5em;
    padding: 0;
}

.cw-messages .cw-knowledge-item:last-child,
.cw-overlay .cw-knowledge-item:last-child {
    margin-bottom: 0;
}

/* Nayttokatto (A165): ylimenevat rivit ovat DOM:ssa mutta piilossa kunnes
   "nayta loput" painetaan. Renderoija poistaa taman luokan, ei lisaa solmuja. */
.cw-messages .cw-knowledge-item--overflow,
.cw-overlay .cw-knowledge-item--overflow {
    display: none;
}

.cw-messages .cw-knowledge-link,
.cw-overlay .cw-knowledge-link {
    color: inherit;
    text-decoration: underline;
    font-weight: 600;
}

.cw-messages .cw-knowledge-title,
.cw-overlay .cw-knowledge-title {
    font-weight: 600;
}

.cw-messages .cw-knowledge-snippet,
.cw-overlay .cw-knowledge-snippet {
    margin-top: 0.15em;
    color: var(--cw-text-muted);
}

/* Kosketuskohde vahintaan 24x24 CSS-pikselia (WCAG 2.2 Target Size, Minimum).
   Koko peritaan kortilta eika aseteta uudelleen: kaksi totuutta ajautuisi
   erilleen. Fokusindikaattori samalla kuviolla kuin `.cw-bubble-toggle`. */
.cw-messages .cw-knowledge-more,
.cw-overlay .cw-knowledge-more {
    display: inline-block;
    min-height: 24px;
    min-width: 24px;
    margin-top: 0.35em;
    padding: 0.2rem 0.4rem;
    background: none;
    border: none;
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    color: var(--cw-text-muted);
    cursor: pointer;
    text-decoration: underline;
}

.cw-messages .cw-knowledge-more:hover,
.cw-overlay .cw-knowledge-more:hover {
    color: var(--cw-text);
}

.cw-messages .cw-knowledge-more:focus-visible,
.cw-overlay .cw-knowledge-more:focus-visible {
    box-shadow: var(--cw-focus-ring);
    border-radius: var(--cw-radius);
    outline: none;
}

/* ===========================================================
 * LEAD FORM — liidilomake (overlayssa, task 393/418)
 * lead_form-ele ja collect_lead avaavat taman.
 * =========================================================== */
.cw-lead-form {
    padding: 12px;
    background: var(--cw-bg);
    border: 1px solid var(--cw-success-border);
    border-radius: var(--cw-radius-lg);
    margin: 8px 0;
    max-width: var(--cw-content-max-width);
}

.cw-lead-form label {
    display: block;
    font-size: 0.85rem;
    color: var(--cw-text);
    margin-bottom: 4px;
    font-weight: 500;
}

/* Kolme tyyppia, ei kahta: lomake luo email/text/tel (chat-widget.js:3482/3502/3519).
   tel puuttui 17.9.2026 asti -> puhelinkentta oli TAYSIN tyylittelematon (ei reunaa,
   ei paddingia, ei fokusrengasta) nimen ja sahkopostin vieressa jokaisella
   istutuksella jolla `phone` on lead_collection.fields-listalla. Jos lisaat neljannen
   kenttatyypin widgetiin, lisaa se MOLEMPIIN valitsinlistoihin (perus + :focus). */
.cw-lead-form input[type="email"],
.cw-lead-form input[type="tel"],
.cw-lead-form input[type="text"] {
    display: block;
    width: 100%;
    /* height: auto — isannan lomaketyyli voi kiinnittaa kentan korkeuden
       (Pico: 65 px), jolloin oma padding ei ratkaise (task 418). */
    height: auto;
    padding: 8px;
    /* A8: oma reunatoken (3:1, ks. --cw-input-border) + tausta ja pyoristys
     * koukkuina. Tausta eksplisiittisesti: isannan lomaketyyli voi muuten
     * antaa kentalle oman. */
    background: var(--cw-input-bg);
    border: 1px solid var(--cw-input-border);
    border-radius: var(--cw-input-radius);
    /* Isanta-CSS:n eristys (A446): isannan kenttasaanto antaa tekstivarin
     * ja appearancen (Pico: oma harmaa + none). Vari tokenista. */
    color: var(--cw-text);
    -webkit-appearance: none;
    appearance: none;
    font-size: 0.9rem;
    /* A579: rivikorkeus myos alle 768 px. Se oli vain tyopoydan media-lohkossa,
     * joten kapealla isannan arvo vuoti (Pico 1.5 -> kentta 34 -> 39,6 px).
     * Luku, ei `normal`: `normal` riippuu fontista, ja fontti tulee isannalta
     * (TYPOGRAFIA) -> korkeus vuotaisi yha (mitattu: 34 -> 38 px). */
    line-height: var(--cw-lead-input-line-height);
    margin-bottom: 10px;
    box-sizing: border-box;
}

.cw-lead-form input[type="email"]:focus,
.cw-lead-form input[type="tel"]:focus,
.cw-lead-form input[type="text"]:focus {
    outline: none;
    box-shadow: var(--cw-focus-ring);
    border-color: var(--cw-primary);
}

/* ===========================================================
 * KAPPALEIDEN KURISTUSPISTE WIDGETIN JUURESSA (aukko A381, 23.9.2026)
 *
 * A380 suojasi kuplan <p>:t isannan `p { margin-bottom: 25px !important }`
 * -saantoa vastaan, mutta kuplan ULKOPUOLISET <p>:t jaivat sen armoille.
 * Mitattu PEL:n tuotannosta 22.9.: `.cw-lead-reason` sai margin-bottom 25px
 * (coren 10px havisi) JA font-size 15.3px (muu chat 14.4px) — molemmat
 * teeman p-saannosta. Per-komponentti-korjaus loytaisi vuodot yksi kerrallaan
 * tuotannosta; tama saanto kattaa luokan kerralla (Teemu-agentin linjaus 22.9.).
 *
 * `.cw-wrapper p` (0,1,1) `!important` voittaa isannan `p` (0,0,1)
 * `!important`in, koska kahden `!important`in kesken ratkaisee spesifisyys.
 *
 * EI YLIAJA KUPLAA: kuplasaannot (`.cw-messages .cw-bubble p` ym., A380/A168)
 * ovat `!important` ja spesifisyydeltaan (0,2,1)+, joten ne voittavat taman
 * jarjestyksesta riippumatta. Oletusarvot ovat samat kuin kuplan kappaleella.
 *
 * 🔴 UUSI p-ELEMENTTI WIDGETTIIN jolla on oma koko tai valistys: saanto
 * tarvitsee `!important` + spesifisyyden > (0,1,1), muuten tama yliajaa sen
 * (ks. `.cw-lead-reason` alla). Arvo tokenina (`var(--cw-…) !important`),
 * ei literaalina, jotta istutus voi saataa sita theme.css:n token-lohkossa.
 *
 * Valistys on sama token kuin kuplan kappaleella (--cw-bubble-p-gap):
 * yksi saato muuttaa molemmat, eika kahta lukua tarvitse pitaa synkassa.
 * =========================================================== */
.cw-wrapper p {
    margin: 0 0 var(--cw-bubble-p-gap) !important;
    padding: 0 !important;
    font-size: inherit !important;
    line-height: inherit !important;
}

/* p-elementti: `.cw-wrapper`-etuliite (0,2,0) + `!important`, ks. yllä */
.cw-wrapper .cw-lead-reason {
    font-size: var(--cw-lead-reason-size) !important;
    color: var(--cw-text-muted);
    margin: 0 0 var(--cw-lead-reason-gap) !important;
}

/* Lapinakyvyyshuomautus liidilomakkeessa: "yhteystiedot menevat LLM:n ohi".
   Hillitympi kuin consent-teksti (se on juridinen ehto, tama on selite) mutta
   luettava — ei harmaa-harmaalla. Muotoilu peilaa .cw-lead-reasonia. */
.cw-lead-ainote {
    font-size: 0.8rem;
    line-height: 1.4;
    color: var(--cw-text-muted);
    margin-bottom: 10px;
}

/* Mallin esitayttamat lisakentat (A469, collect_lead -> result.extra): kavija
   nakee mita myyjalle valitetaan. Luku, ei syote. Muotoilu peilaa .cw-lead-reasonia. */
.cw-lead-prefill {
    font-size: var(--cw-lead-reason-size);
    line-height: 1.4;
    color: var(--cw-text-muted);
    margin-bottom: var(--cw-lead-reason-gap);
}
.cw-lead-prefill-label {
    font-weight: 600;
}

.cw-lead-checkbox {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-bottom: 10px;
}

/* Natiiviruksi kiinnitettyna, koska isannan checkbox-tyyli ei tunne widgettia
 * (task 418, 23.9.): Pico asettaa `appearance: none` + oman koon, ja entinen
 * `width: auto` nollasi sen -> ruksi 4 px leveä eli nakymaton, suostumus
 * mahdoton antaa. `.cw-wrapper`-etuliite (0,3,1) voittaa isannan
 * attribuutti-/pseudovalitsimet ilman !importantia. appearance: auto palauttaa
 * selaimen oman ruksin, jolloin isannan tausta/reuna/kuva eivat piirry. */
.cw-wrapper .cw-lead-checkbox input[type="checkbox"] {
    -webkit-appearance: auto;
    appearance: auto;
    width: var(--cw-lead-check-size);
    height: var(--cw-lead-check-size);
    min-width: var(--cw-lead-check-size);
    margin: 3px 0 0;
    padding: 0;
    /* Natiiviruksi ei piirra naita, mutta isanta asettaa ne (Pico: tausta,
     * vari, fonttikoko, vertical-align) — kiinnitetaan, jotta valjas
     * (test-widget-host-css.js) nakee todellisen vuodon eika kohinaa (A446). */
    background-color: transparent;
    color: inherit;
    font-size: inherit;
    vertical-align: baseline;
    accent-color: var(--cw-primary);
    flex-shrink: 0;
}

.cw-lead-checkbox label {
    display: inline;
    font-weight: 400;
    margin-bottom: 0;
}

.cw-lead-checkbox a {
    color: var(--cw-primary);
    text-decoration: underline;
}

.cw-lead-form button {
    padding: 8px 16px;
    background: var(--cw-primary);
    color: var(--cw-on-primary);
    border: none;
    border-radius: var(--cw-radius);
    cursor: pointer;
    font-size: 0.9rem;
    /* A579: ks. kenttasaanto yllä — ilman tata Pico kasvatti nappia 32 -> 37,6 px kapealla. */
    line-height: var(--cw-lead-input-line-height);
    /* Kaikki reunat: isannan button-saanto voi tuoda alamarginaalin (Pico 1rem). */
    margin: 4px 0 0;
}

.cw-lead-form button:hover {
    background: var(--cw-primary-hover);
}

.cw-lead-form button:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.cw-lead-success {
    color: var(--cw-success-text);
    font-weight: 500;
    padding: 8px 0;
}

.cw-lead-error {
    color: var(--cw-error);
    font-size: 0.85em;
    min-height: 1.2em;
}

/* Lead-CTA-nappi viestivirrassa (.cw-lead-cta, AUTO:167) poistui task 418:ssa:
   collect_lead nostaa nyt lead_form-eleen palkkiin ja avaa overlayn (spec/ele.md). */

/* ===========================================================
 * OVERLAY (task 393, aukko A41 osa c, spec/ele.md § 4)
 *
 * MIKSI: lomakekentta menee kantaan, chat-viesti mallille — lomake on OMA
 * PINTANSA, ei puhekupla. Ankkuri on .cw-panel (position: relative):
 * overlay peittaa paneelin headerin ja viestivirran, mutta EI syoterivia —
 * paneelin alaosa (--cw-panel-dock-overlap) on syoterivin takana, joten
 * overlay loppuu siihen. Upotetussa moodissa limitysta ei ole -> bottom: 0.
 * Perustelu JS:n puolella: OVERLAY-lohko chat-widget.js:ssa.
 * Istutus saa yliajaa --cw-overlay-* -tokeneilla, ei tata tiedostoa muokkaamalla.
 * =========================================================== */
.cw-wrapper:not([data-cw-mode="embed"]) .cw-panel--overlay {
    /* Overlay tarvitsee korkeutta vaikka paneeli olisi matalassa fokustilassa
     * (vain tervehdys). Poistuu sulkiessa -> paneeli palaa entiseen (eri kuin
     * kertalukitus .cw-panel--full, joka on kayttajan lahetyksen seuraus). */
    height: min(var(--cw-overlay-max-height), 85vh);
    max-height: min(var(--cw-overlay-max-height), 85vh);
}

.cw-overlay {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: var(--cw-panel-dock-overlap);
    z-index: 3;
    display: flex;
    flex-direction: column;
    background: var(--cw-overlay-bg);
    border: 1px solid var(--cw-overlay-border);
    border-bottom: none;
    border-radius: var(--cw-overlay-radius) var(--cw-overlay-radius) 0 0;
    box-shadow: var(--cw-overlay-shadow);
    box-sizing: border-box;
    overflow: hidden;
}

.cw-overlay-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 0.5rem 0.6rem 1rem;
    border-bottom: 1px solid var(--cw-overlay-border);
    flex: 0 0 auto;
}

.cw-overlay-title {
    flex: 1 1 auto;
    margin: 0;
    /* <h2> perisi muuten isannan otsikkofontin (PEL: Russo One 600) — widget
     * perii leipatekstin fontin kuten muualla (A7). Isannan mahdollinen
     * `h2 { margin … !important }` on isantakohtainen eika sita kumota tassa. */
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--cw-text);
    line-height: 1.3;
}

/* X oikeassa ylakulmassa (Teemu 17.9.2026). margin-left: auto pitaa sen
 * oikealla myos kun otsikkoa ei ole. 44x44 kosketuskohde (WCAG 2.5.5).
 * margin + padding eksplisiittisesti: isannan button-saanto (Pico: padding
 * .75rem 1rem + margin-bottom) kasvatti headerin 53 px -> 94 px (task 418). */
.cw-wrapper .cw-overlay-close {
    margin: 0 0 0 auto;
    padding: 0;
    flex: 0 0 auto;
    min-width: var(--cw-overlay-close-size-touch);
    min-height: var(--cw-overlay-close-size-touch);
    /* Ikoni maskina (task 419, A2): sama SVG-ruksi kuin otsikkopalkissa. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    border-radius: var(--cw-radius);
    color: var(--cw-text-muted);
    font-size: 0;
    line-height: 1;
    cursor: pointer;
    font-family: inherit;
    --cw-icon: var(--cw-icon-close);
    --cw-icon-box: var(--cw-overlay-close-icon-size);
}
.cw-wrapper .cw-overlay-close:hover {
    color: var(--cw-text);
}
.cw-wrapper .cw-overlay-close:focus-visible {
    outline: none;
    box-shadow: var(--cw-focus-ring);
}

/* Sisalto vierii (pitka lomake 375 px:ssa), header pysyy. */
.cw-overlay-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;   /* A704: ks. .cw-messages */
    padding: 0.75rem 1rem 1rem;
}

/* NAKYMA-ELE (task 519, A398): sivupakka overlayssa + kuplan avauspainike.
 * Hakutulosten lahdelista (.cw-knowledge-*) kayttaa samoja saantoja kuin virrassa
 * (.cw-overlay-etuliite rinnalla, TOOL-TULOSKORTTI-lohko). Napit ovat
 * eristyslohkossa (.cw-wrapper-etuliite), joten omat saannot tulevat sen jalkeen. */
.cw-overlay .cw-view {
    font-size: 0.8rem;
    line-height: var(--cw-bubble-line-height);
    color: var(--cw-text);
}
.cw-overlay .cw-view-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}
.cw-overlay .cw-view-counter {
    color: var(--cw-text-muted);
}
.cw-wrapper .cw-view-prev,
.cw-wrapper .cw-view-next {
    min-width: 32px;
    min-height: 32px;
    padding: 0 0.5rem;
    background: none;
    border: 1px solid var(--cw-border);
    border-radius: var(--cw-radius);
    font-family: inherit;
    font-size: 1rem;
    line-height: 1;
    color: var(--cw-text);
    cursor: pointer;
}
.cw-wrapper .cw-view-prev:disabled,
.cw-wrapper .cw-view-next:disabled {
    opacity: 0.4;
    cursor: default;
}
.cw-wrapper .cw-view-prev:focus-visible,
.cw-wrapper .cw-view-next:focus-visible,
.cw-wrapper .cw-view-reopen:focus-visible {
    outline: none;
    box-shadow: var(--cw-focus-ring);
}
.cw-overlay .cw-view-slide-title {
    margin: 0 0 0.5rem;
    font-size: 0.9rem;
    font-weight: 600;
}
.cw-overlay .cw-view-html > :first-child {
    margin-top: 0;
}
.cw-overlay .cw-view-html > :last-child {
    margin-bottom: 0;
}
.cw-overlay .cw-view-empty {
    margin: 0;
    color: var(--cw-text-muted);
}
/* Kuplan avauspainike: sivu jonka vastaus nosti (paatos 4). Kosketuskohde >= 24 px. */
.cw-wrapper .cw-view-reopen {
    display: inline-block;
    margin-top: 0.4em;
    min-height: 24px;
    padding: 0.2rem 0.5rem;
    background: none;
    border: 1px solid var(--cw-border);
    border-radius: var(--cw-radius);
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    color: var(--cw-text);
    cursor: pointer;
}
.cw-wrapper .cw-view-reopen:hover {
    border-color: var(--cw-text-muted);
}

/* Liidilomake overlayssa: pinta on jo kortti -> ei toista korttia sisaan. */
.cw-overlay .cw-lead-form {
    padding: 0;
    margin: 0;
    background: transparent;
    border: none;
    max-width: none;
}

/* TIIVIS LOMAKE TYOPOYDALLA (task 418, kohta 7 — Teemu 23.9.: "mahtuis
 * kokonaan näkyviin tohon chat widgetin varaamalle alueelle, ettei tarvi
 * vierittää ruutua […] kentät inline tjsp, ei megaluokan paddingeja").
 *
 * Label + kentta samalle riville kaksisarakkeisella gridilla. DOM-jarjestys
 * (label, input, label, input, ...) sijoittuu sarakkeisiin itsestaan; muut
 * lapset (syy, huomautus, ruksit, nappi, virhe) koko levylta. Sisainen
 * vieritys (.cw-overlay-body) jaa vain aariatapaukseen (pitka syyteksti,
 * iso fontti). Mobiili ennallaan: kapealla ruudulla pino on luettavampi.
 * Kerros 3 saataa --cw-lead-* ja --cw-overlay-body-padding -tokeneilla. */
@media (min-width: 768px) {
    .cw-overlay .cw-overlay-header {
        padding: var(--cw-overlay-header-padding);
    }
    /* Hiirella 44 px kosketuskohde (2.5.5) vie headerista turhaa korkeutta;
     * tokenin oletus 2.25rem = 36 px > WCAG 2.5.8:n 24 px. Mobiili ennallaan. */
    .cw-overlay .cw-overlay-close {
        min-width: var(--cw-overlay-close-size);
        min-height: var(--cw-overlay-close-size);
    }
    .cw-overlay .cw-overlay-body {
        padding: var(--cw-overlay-body-padding);
    }
    .cw-overlay .cw-lead-form {
        display: grid;
        grid-template-columns: var(--cw-lead-label-width) minmax(0, 1fr);
        gap: var(--cw-lead-gap);
        align-items: center;
    }
    .cw-overlay .cw-lead-form > * {
        grid-column: 1 / -1;
    }
    .cw-overlay .cw-lead-form > label,
    .cw-overlay .cw-lead-form > input {
        grid-column: auto;
        margin: 0;
    }
    .cw-overlay .cw-lead-form input[type="email"],
    .cw-overlay .cw-lead-form input[type="tel"],
    .cw-overlay .cw-lead-form input[type="text"] {
        padding: var(--cw-lead-input-padding);
        line-height: var(--cw-lead-input-line-height);
    }
    /* Tyhja virherivi ei varaa riviä: aria-live-alue pysyy DOMissa ja
     * nakyvana (ei display:none), vain korkeus kasvaa kun virhe tulee. */
    .cw-overlay .cw-lead-error {
        min-height: 0;
    }
    .cw-overlay .cw-lead-ainote,
    .cw-overlay .cw-lead-checkbox {
        margin-bottom: 0;
    }
    .cw-overlay .cw-lead-form button {
        justify-self: start;
        margin-top: 0;
        line-height: var(--cw-lead-input-line-height);
    }
    /* Elepalkki pois overlayn ajaksi. Overlay paattyy syoterivin ylareunaan
     * (bottom: --cw-panel-dock-overlap = mitattu rivin korkeus), ja palkki on
     * osa rivia -> jokainen ele vei ~60 px lomakkeelta (mitattu 23.9.: 1 ele
     * 110 px, 2 eletta 115 px vieritysta). Overlay ei voi peittaa palkkia:
     * syoterivi (z 2) on paneelin (z 1) edessa. Kavija on jo lomakkeessa;
     * palkki palaa kun overlay suljetaan. Luokan asettaa openOverlay ja
     * poistaa closeOverlay ENNEN fokuksen palautusta palkin nappiin. */
    .cw-wrapper.cw-overlay-open:not([data-cw-mode="embed"]) .cw-actionbar {
        display: none;
    }
}

/* Rullaverho (task 418, 8c). Paneeli leikkaa pinnan reunaansa, joten se
 * nousee syoterivin takaa eika piirry sen paalle. Luokat asettaa JS vain kun
 * liike on sallittu (overlayMotionOn); reduced motion -lohko on turvaverkko. */
.cw-panel--overlay {
    overflow: hidden;
}

.cw-overlay--opening {
    animation: cw-overlay-rise var(--cw-overlay-duration) var(--cw-overlay-easing) both;
}

.cw-overlay--closing {
    animation: cw-overlay-fall var(--cw-overlay-duration) var(--cw-overlay-easing) both;
    pointer-events: none;
}

@keyframes cw-overlay-rise {
    from { transform: translateY(100%); }
    to { transform: translateY(0); }
}

@keyframes cw-overlay-fall {
    from { transform: translateY(0); }
    to { transform: translateY(100%); }
}

.cw-wrapper[data-cw-mode="embed"] .cw-overlay {
    bottom: 0;
    border-bottom: 1px solid var(--cw-overlay-border);
    border-radius: var(--cw-overlay-radius);
}

/* ===========================================================
 * UPOTETTU MOODI (aukko A12) — chat sivun sisalla, ei kiinnitettya dokkia
 *
 * MIKSI ATTRIBUUTTI EIKA LUOKKA: moodi ei saa tuottaa omia luokkanimia. Jos
 * upotettu chat kayttaisi esim. `.cw-wrapper--embed`ia, jokainen istutus
 * kirjoittaisi teemansa kahdesti (dokille ja upotetulle) ja ne ajautuisivat
 * erilleen — eli syntyisi tasan se drift jota vastaan tama ominaisuus on
 * olemassa. Attribuuttina luokkanimijoukko on identtinen molemmissa moodeissa
 * ja teema kirjoitetaan kerran. Vahti: app/test/test-widget-embed.js.
 *
 * MIKSI YHDISTELMAVALITSIN (`.cw-wrapper[data-cw-mode="embed"]`) EIKA PELKKA
 * ATTRIBUUTTI: pelkka `[data-cw-mode="embed"]` on samaa spesifisyytta kuin
 * `.cw-wrapper`, jolloin jarjestys tiedostossa ratkaisisi — ja mobiilin
 * media query (alla) yliajaa `.cw-panel`in MYOHEMMIN. Yhdistelma nostaa
 * spesifisyyden niin etta moodi voittaa sijainnista riippumatta. Tama ei ole
 * spesifisyyskilpailu vaan sen VALTTAMINEN: sannon paikka tiedostossa lakkaa
 * olemasta merkitsevaa.
 *
 * KORKEUS ON SAILION ASIA, EI WIDGETIN: istutus antaa kontille korkeuden
 * (hero-lohko: `.dg-chat-hero__chat-container { height: 32rem }`). Jos kontilla
 * ei ole korkeutta, `height: 100%` resolvoituu autoksi ja widget kasvaa
 * sisallon mukana — degradaatio on siis nakyva eika rikkinainen.
 * =========================================================== */
.cw-wrapper[data-cw-mode="embed"] {
    /* Pois kiinnitetysta asemoinnista: widget virtaa sivun mukana. `bottom`,
     * `left`, `right` ja `z-index` nollataan eksplisiittisesti — base-saannon
     * `bottom: var(--cw-dock-offset, ...)` periytyisi muuten myos tanne ja
     * siirtaisi lohkoa evastebannerin mukaan keskella sivua. */
    position: relative;
    bottom: auto;
    left: auto;
    right: auto;
    z-index: auto;
    width: 100%;
    height: 100%;
    /* Dokissa wrapper on lapinakyva klikkauksille (se on overlay tyhjine
     * alueineen). Upotettuna se ON sivun sisaltoa, joten se ottaa klikkaukset. */
    pointer-events: auto;
}

.cw-wrapper[data-cw-mode="embed"] .cw-panel {
    /* Dokissa paneeli on rajattu laatikko jonka korkeus on tokenoitu ja joka
     * liukuu kapseli-inputin TAAKSE (negatiivinen margin + padding). Upotettuna
     * paneeli ja syoterivi ovat allekkain, joten molemmat kumotaan. */
    max-width: none;
    max-height: none;
    flex: 1 1 auto;
    /* 🔴 min-height: 0 on pakollinen, ei kosmetiikkaa: flex-lapsen oletus on
     * `min-height: auto`, jolloin paneeli EI kutistu sisaltoaan pienemmaksi ja
     * `.cw-messages`in `overflow-y: auto` ei aktivoidu koskaan — pitka keskustelu
     * venyttaisi hero-lohkon sivun mittaiseksi scrollaamisen sijaan. */
    min-height: 0;
    margin: 0;
    padding-bottom: 0;
    /* Sama koukku kuin dokissa (A13): istutus nollaa reunan tokenilla eika
     * tarvitse tata (0,3,0)-saantoa voittavaa valitsinta. */
    border-bottom: var(--cw-panel-border);
    border-radius: var(--cw-radius-panel);
}

.cw-wrapper[data-cw-mode="embed"] .cw-input-row {
    max-width: none;
    padding: 0.5rem 0 0;
}

/* ===========================================================
 * MOBIILI (<576px): full-width, kiinni pohjassa
 * =========================================================== */
@media (max-width: 575.98px) {
    /* Nayttamohahmo (task 403) pois kapealta ruudulta: tayslevea paneeli on
     * ~360 px, ja 8rem hahmo veisi siita kolmanneksen keskustelulta. Hahmo on
     * koriste, keskustelu ei. Coren raja on tama sama breakpoint kuin muulla
     * mobiililla; istutus voi levittaa sen leveammalle (PEL: 768 px) tai
     * palauttaa hahmon theme.css:llaan. */
    .cw-stage-avatar {
        display: none;
    }
    /* Elepalkki (A41): mobiilissa enintaan ~2 rivia, loput vierittyvat —
     * palkki ei saa tyontaa syotekenttaa pois nakyvista. */
    .cw-actionbar {
        max-height: var(--cw-ele-max-height-mobile, 4.5rem);
    }
    /* Siirtotarjouspalkki (A470): napit omalle rivilleen ja palkki matalammaksi.
     * Auki oleva nappaimisto vie ruudusta puolet; palkki on syoterivin lapsi
     * kentan YLAPUOLELLA, joten se ei voi peittaa kenttaa — max-height pitaa
     * huolen ettei se myoskaan tyonna kenttaa ruudun ylalaidan yli (palkki
     * vierittyy, kentta ei liiku). */
    .cw-handoff {
        max-height: var(--cw-handoff-max-height-mobile, 6.5rem);
    }
    .cw-handoff__actions {
        flex: 1 1 100%;
    }
    .cw-wrapper {
        /* SAMA KETJU KUIN BASE-SAANNOSSA (task 279) — ei `bottom: 0`.
         * Tama rivi oli ennen `bottom: 0`, mika on sama spesifisyys ja MYOHEMMIN
         * tiedostossa → se olisi kumonnut vaiston tasan mobiilissa, eli siella
         * missa alapalkin ruuhka on pahin. Mikaan ei olisi virheillyt: ominaisuus
         * olisi nayttanyt toimivan tyopoydalla ja ollut inertti puhelimessa. */
        bottom: var(--cw-dock-offset, var(--cw-dock-auto, 0px));
    }

    /* A705 (Teemu 1.10.2026, DEMO): syotekentta on mobiilissakin PILLERI kuten
     * tyopoydalla. Aiemmin tama lohko litisti dokin kapselin reunattomaksi
     * palkiksi (radius 0, vain ohut ylaviiva, ei varjoa), eika kentta erottunut
     * ruudusta. Nyt kapseli on perussaanto sellaisenaan, ja rivin pehmuste
     * irrottaa sen ruudun reunoista. Upotetun moodin oma `.cw-input-row`
     * (0,2,0) voittaa taman (0,1,0) kuten ennenkin.
     *
     * 🔴 ALA PALAUTA KAPSELIN RIISUNTAA TANNE: mobiilin `.cw-input-inner`-saanto
     * joka asettaa `box-shadow: none` tai reunat pois kilpailee fokussaantojen
     * kanssa spesifisyydesta (A32/A7, H2-css-40). Vahti:
     * test-widget-mobile-ux-css.js P-sarja. */
    .cw-input-row {
        max-width: none;
        padding: 0 0.5rem 0.5rem;
    }

    /* A706: leipateksti ja syote luettaviksi puhelimessa (ks. tokenit).
     * Sama spesifisyys kuin perussaannot ja myohemmin -> voittaa. Kuplan
     * otsikot, kappalevalit ja kollapsointi ovat em-pohjaisia ja seuraavat. */
    .cw-messages .cw-bubble {
        font-size: var(--cw-bubble-font-size-mobile);
    }
    .cw-textarea {
        font-size: var(--cw-input-font-size-mobile);
    }

    /* FOKUS MOBIILISSA: tummempi reuna, EI sinista rengasta (Teemu 1.10.2026,
     * ks. --cw-input-border-focus-mobile). Korvaa perussaannon
     * `.cw-input-inner:focus-within` box-shadow'n: sama spesifisyys (0,2,0) ja
     * MYOHEMMIN tiedostossa -> voittaa. Lepovarjo sailyy kuten perussaannossa.
     * border-color (0,2,0) voittaa kapselin perusreunan (0,1,0): pillerin
     * koko reuna tummuu (A705), dokissa ja upotetussa moodissa samoin. */
    .cw-input-inner:focus-within {
        box-shadow: var(--cw-shadow-sm);
        border-color: var(--cw-input-border-focus-mobile);
    }

    /* Mobiili: ikonit riittavan isot sormille (min 44x44px). Sama (0,2,0)
     * kuin perussaanto, muuten media query haviaisi sille (A468). */
    .cw-wrapper .cw-input-action {
        min-width: 2.75rem;
        min-height: 2.75rem;
        font-size: 1.2rem;
    }

    .cw-panel {
        max-width: none;
        /* 🔴 VAAKAMARGINAALIT VAIN — EI `margin: 0` (aukko A47, 18.9.2026).
         *
         * Tama rivi oli `margin: 0`, jonka TARKOITUS on tayslevea paneeli eli
         * vaakamarginaalien (`0 0.75rem`) nollaus. Se nollasi samalla myos
         * ALAMARGINAALIN, joten limitysta ei ollut mobiilissa lainkaan:
         * paneelin pohjalla oli 4.5rem kuollutta padding-tilaa ja syoterivi sen
         * ALLA. Sama vikaluokka kuin `.cw-wrapper`in `bottom: 0` ylla — lyhenne
         * joka nollaa enemman kuin tarkoittaa, ja seuraus nakyy vain mobiilissa.
         *
         * MIKSI TAMA ON NYT PAKKO KORJATA EIKA VAIN KANNATTAVAA: kun limitys
         * muuttui vakiosta muuttujaksi, `padding-bottom` KASVAA syoterivin
         * mukana. Jos alamarginaali jaisi nollaan, mikaan ei enaa kompensoisi
         * kasvua ja mobiili olisi kahta huonompi kuin ennen (paneeli kasvaa
         * ΔR JA syoterivi kasvaa ΔR). Vaihtoehto olisi ollut jaadyttaa
         * mobiilin padding vakioon, mutta se jattaisi A47:n korjaamatta juuri
         * siella missa monirivinen kirjoittaminen on yleisinta — kapealla
         * ruudulla rivi katkeaa aiemmin.
         *
         * ⚠️ TAMA ON NAKYVA ULKOASUMUUTOS MOBIILISSA: paneeli liukuu nyt
         * syoterivin taakse kuten tyopoydalla, ja pohjan 4.5rem kuollut tila
         * katoaa. EI VERIFIOITU SELAIMESSA — ks. feedback/koodari-298. */
        margin-left: 0;
        margin-right: 0;
        border-radius: 0;
        border-left: none;
        border-right: none;
    }

    /* Overlay seuraa paneelin mobiiliasua (tayslevea, ei pyoristysta). */
    .cw-overlay {
        border-left: none;
        border-right: none;
        border-radius: 0;
    }
}

/* ===========================================================
 * REDUCED MOTION -SUOJA (H1-css-3)
 * Vestibulaarihairioille liike on oire, ei koriste. Kaikki widgetin
 * transitiot ja animaatiot pois kun kayttaja on pyytanyt sita.
 * =========================================================== */
@media (prefers-reduced-motion: reduce) {
    /* .cw-wrapper (task 281): saapumisanimaatio. JS ei lisaa luokkaa lainkaan kun
     * asetus on paalla, mutta tama on turvaverkko sille etta asetus vaihtuu
     * kesken istunnon. Huomioita tavoitteleva liike on juuri sita lajia joka
     * haittaa vestibulaarioireisia. .cw-input-inner ja .cw-send-btn olivat jo
     * listalla, joten laskeutumisvarjo ja napin valahdys kattuvat niiden alle. */
    .cw-wrapper,
    .cw-input-inner,
    .cw-send-btn,
    .cw-input-action,
    .cw-conversation-item,
    .cw-typing span,
    /* .cw-agent-typing__dot (18.9.2026): myyjan kirjoitusindikaattori ajaa
       SAMAA `cw-bounce`-animaatiota `infinite`-toistolla kuin `.cw-typing span`,
       mutta se jai listalta pois kun myyjan merkki erotettiin omaksi
       elementikseen (task 215a). Puuttuminen oli taysin hiljainen: asetus on
       paalla, listan muut rivit toimivat, ja ainoa oire on etta kolme pallukkaa
       pomppii loputtomasti juuri sille kayttajalle joka on pyytanyt etta ei
       pomppisi. Vahti: test-widget-focus-css.js F-sarja (kattavuusvertailu). */
    .cw-agent-typing__dot,
    /* .cw-stream-loader span (A393, task 408): sama cw-bounce infinite. */
    .cw-stream-loader span,
    /* .cw-overlay--opening/--closing (task 418, 8c): rullaverho. JS ei lisaa
       luokkia kun asetus on paalla; tama kattaa kesken istunnon vaihdon. */
    .cw-overlay--opening,
    .cw-overlay--closing,
    /* .cw-ele--flash (A448): eleen nousu nostohetkella. */
    .cw-ele--flash,
    /* .cw-tool-status::before (23.9.2026): pyoriva rengas / istutuksen hahmo.
       JS ei myoskaan vaihda tilarivin tekstia kun asetus on paalla. */
    .cw-tool-status::before {
        transition: none !important;
        animation: none !important;
    }
}

/* ===========================================================
 * A31: TORJUTTU MOUNT-KONTTI (data-chatcore-unmounted)
 *
 * MIKA KIRJOITTAA: kolme kirjoittajaa, yksi sopimus. (1) chat-widget.js:n
 * init() kun se torjuu toisen kutsun (widget on singleton, ks. sen kommentti),
 * (2) [guided-chat-widget.js, poistettu task 470], (3) kerros 2:n dg-chat-boot.js
 * niille mount-pisteille jotka se jattaa mounttaamatta. Siksi attribuutin nimi
 * on PREFIKSITON (`data-chatcore-*`, ei `cw-`/`gc-`): kumpikaan widget-prefiksi
 * ei kattaisi kaikkia kolmea.
 *
 * MIKSI TAMA SAANTO TARVITAAN: torjuttu kontti jaa TYHJAKSI, ja tyhja div ei
 * renderoi mitaan itsestaan — mutta kerros 3 on usein tyylitellyt sen.
 * RengasCenterin mu-plugin varoittaa tasmalleen tasta ("julkinen nakisi tyhjan
 * sinisen chat-palkin"), eli oire on mitattu tuotannossa eika kuviteltu.
 * Ilman tata saantoa vika olisi juuri sita lajia jota kukaan ei yhdista
 * konsolivaroitukseen: sivulla nakyy tyhja varillinen palkki.
 *
 * MIKSI !important: saanto kilpailee ISANTATEEMAN kanssa kontista jota core ei
 * omista (istutus antaa sen, teema voi tyylitella sen millaisella
 * spesifisyydella tahansa). Tavallinen `display: none` havisi yhdelle
 * `#id .luokka { display: flex }` -riville, ja haviaminen olisi hiljaista.
 *
 * SOPIMUS JOHON TAMA NOJAA: mount-kontti on CHATILLE OMISTETTU elementti —
 * core tekee siihen `container.appendChild()`. Siksi sen piilottaminen ei voi
 * piilottaa muuta sisaltoa. Jos istutus antaa mountiksi koko hero-sektion,
 * se on konfiguraatiovirhe, ja tama saanto tekee sen NAKYVAKSI (sektio
 * katoaa) sen sijaan etta chat rakentuisi vaaran elementin sisaan hiljaa.
 * =========================================================== */
[data-chatcore-unmounted] {
    display: none !important;
}

/* ===========================================================
 * KUVATULOKSET (addImageResult) — aukko A138, task 411
 *
 * Core-API addImageResult() renderoi .cw-image-result + .cw-image-<tyyppi>
 * -kuplan ja sidoo .cw-image-action-linkit, mutta tyylit asuivat aidin
 * projektikerroksessa (app/public/css/base.css, --ap-*-tokenit). Kerros 1:n
 * API ilman kerros 1:n tyyleja = istutus saa tyylittomat kuplat. Arvot ovat
 * samat kuin --ap-*:lla, joten ajopaivakirjan ulkoasu ei muutu.
 *
 * Tiedoston LOPUSSA tarkoituksella: saannot olivat ennen base.css:ssa, joka
 * ladataan chat-widget.css:n JALKEEN — sama kaskadijarjestys sailyy.
 * =========================================================== */
.cw-image-result {
    line-height: 1.5;
}

.cw-image-result strong {
    font-weight: 600;
}

/* Tilat */
.cw-image-confirmed {
    border-left: 3px solid var(--cw-success);
    padding-left: 0.6rem;
}

.cw-image-pending {
    border-left: 3px solid var(--cw-warning);
    padding-left: 0.6rem;
}

.cw-image-error {
    border-left: 3px solid var(--cw-error);
    padding-left: 0.6rem;
}

.cw-image-uploading,
.cw-image-processing {
    border-left: 3px solid var(--cw-border);
    padding-left: 0.6rem;
}

/* Progress-palkki */
.cw-image-progress {
    height: 3px;
    background: var(--cw-border);
    border-radius: 2px;
    margin-top: 0.4rem;
    overflow: hidden;
}

.cw-image-progress-bar {
    height: 100%;
    background: var(--cw-primary);
    border-radius: 2px;
    width: var(--progress, 0%);
    transition: width 0.3s;
}

.cw-image-progress-bar.cw-indeterminate {
    width: 30% !important;
    animation: cw-indeterminate 1.2s ease-in-out infinite;
}

@keyframes cw-indeterminate {
    0% { transform: translateX(-100%); }
    100% { transform: translateX(400%); }
}

/* Tiedostonimi / status */
.cw-image-filename {
    font-size: 0.8rem;
    color: var(--cw-text-muted);
}

/* Ikonit */
.cw-image-check { color: var(--cw-success); }
.cw-image-error-icon { color: var(--cw-error); }
.cw-image-warn-icon { color: var(--cw-warning); }
.cw-image-photo-icon { color: var(--cw-text-muted); }

/* Epavarma lukema */
.cw-image-uncertain {
    color: var(--cw-warning);
    font-style: italic;
}

/* Backfill-info */
.cw-image-backfill-info {
    margin-top: 0.4rem;
    font-size: 0.75rem;
    color: var(--cw-text-muted);
    padding: 0.3rem 0.5rem;
    background: var(--cw-success-bg);
    border-radius: var(--cw-radius);
}

/* Jaljella olevat aukot */
.cw-image-gaps-info {
    margin-top: 0.3rem;
    font-size: 0.75rem;
    color: var(--cw-warning);
}

/* Varoitukset */
.cw-image-warning {
    margin-top: 0.25rem;
    font-size: 0.75rem;
    color: var(--cw-warning);
}

/* Toimintonapit */
.cw-image-actions {
    margin-top: 0.5rem;
    display: flex;
    gap: 0.4rem;
}

/* "Ei oikein?" -linkki */
.cw-image-action {
    color: var(--cw-text-muted);
    text-decoration: underline;
    cursor: pointer;
    font-size: 0.75rem;
}

.cw-image-action:hover {
    color: var(--cw-text);
}

/* Error-yhteenveto */
.cw-image-error-summary {
    margin-top: 0.3rem;
    font-size: 0.75rem;
    color: var(--cw-error);
}
