/* ============================================================
   MARK — HALLINTAPANEELI 2.0  (2026-07-27)
   Ladataan app.css:n JÄLKEEN. Perutaan poistamalla tämä tiedosto
   ja sen <link> dashboard.html:stä.

   Suunnitteluperiaate (ei "tumma pohja + yksi aksentti", joka on
   tunnistettu oletusklusteri): KOLME väriä, jokaisella oma rooli.

     TEKO      spektri (amber→rose→violetti) — vain toiminnot ja
               Markin oma ääni. Säästeliäästi.
     DATA      teal  #34C2AE — mitattu, vahvistettu, elossa.
     ILMAPIIRI violetti #9B7CF0 — Markin läsnäolo ja ajattelu.

   Muut periaatteet: kerroksellinen tausta (mesh + rasteri + rae),
   typografian ääripäät (200 vastaan 700, otsikkohyppy yli 5×),
   yksi orkestroitu sisääntulo, tietoisesti eri pyöristykset.
   ============================================================ */

:root {
  --d-bg:      #0C0A08;
  --d-surface: rgba(245,240,232,0.035);
  --d-raised:  rgba(245,240,232,0.06);
  --d-hair:    rgba(245,240,232,0.09);
  --d-hair-2:  rgba(245,240,232,0.16);

  /* Lapinakymaton pinta modaaleille. --d-surface ja --d-raised ovat
     lapinakyvia savyja ladottavaksi --d-bg:n paalle SIVUN SISALLA. Kelluva
     paneeli ei ole sivun sisalla: sen alla on mielivaltaista sisaltoa, ja
     6 % peittavyys tarkoittaa etta 94 % siita mita nakyy on alla oleva
     sisalto. Mitattu 9.8.2026: keskustelupaneeli oli lukukelvoton juuri
     tasta syysta. Modaalille kuuluu oma lapinakymaton token. */
  --d-modal:   #17130F;

  --act:  linear-gradient(118deg, #F7B36B 0%, #EC7FA2 45%, #B98BE6 100%);
  /* --act on GRADIENTTI. Se kelpaa vain backgroundiksi. Jos tarvitset
     korostuksen viivaksi, reunaksi tai fokusrenkaaksi, kayta tata:
     gradientti color-paikassa on kelvoton arvo ja selain hylkaa koko
     saannon aanettomasti. Nain kaatui neljä saantoa 9.8.2026 asti. */
  --act-line: #B98BE6;
  --data: #34C2AE;
  --amb:  #9B7CF0;

  /* PYORISTYSASTEIKKO, taydennetty 26.8.2026.
     Lahtotilanne oli 12 eri raakaa arvoa (2, 4, 8, 9, 10, 12, 16, 18, 20,
     50%, 100, 999) ja kaksi tokenia joita kaytettiin 19 kohdassa 41:sta.
     Nelja tokenia riittaa: pilleri, paneeli, kentta ja hiuspyoristys.
     50% (ympyra) ja 0 (ei pyoristysta) eivat ole asteikon askelmia vaan
     muotoja, joten ne jaavat literaaleiksi. */
  --r-pill:  999px;   /* pillerinapit, tagit; korvaa myos vanhan 100px */
  --r-panel: 18px;    /* paneelit ja isot kortit; korvaa 16 ja 20 */
  --r-field: 12px;    /* kentat, napit, pienet kortit; korvaa 8, 9, 10 */
  --r-xs:    3px;     /* hiuspyoristys, merkit; korvaa 2 ja 4 */
}

/* ---------------- Kerroksellinen tausta ----------------
   Litteä yksivärinen pohja on oletus. Tässä on kolme kerrosta:
   pehmeä mesh (ilmapiiri), hieno rasteri (mittalaite) ja rae. */
body {
  background: var(--d-bg);
  font-synthesis-weight: none;
}
.app { position: relative; isolation: isolate; }
.app::before,
.app::after { content: ""; position: fixed; inset: 0; pointer-events: none; z-index: -1; }
.app::before {
  background:
    radial-gradient(48% 42% at 12% 4%,  rgba(155,124,240,0.16) 0%, transparent 70%),
    radial-gradient(46% 44% at 92% 88%, rgba(52,194,174,0.13) 0%, transparent 72%),
    radial-gradient(38% 30% at 62% 28%, rgba(236,127,162,0.06) 0%, transparent 70%);
}
.app::after {
  background-image:
    linear-gradient(rgba(245,240,232,0.16) 1px, transparent 1px),
    linear-gradient(90deg, rgba(245,240,232,0.16) 1px, transparent 1px),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.35'/%3E%3C/svg%3E");
  background-size: 64px 64px, 64px 64px, 160px 160px;
  opacity: 0.16;
}

/* ---------------- Sivupalkki ---------------- */
.side {
  background: rgba(12,10,8,0.72);
  border-right: 1px solid var(--d-hair);
}
.side .brand { font-weight: 600; letter-spacing: -0.015em; }
.biz b { font-family: var(--body); font-weight: 700; font-size: 13px; letter-spacing: 0.01em; }
.biz span { font-family: var(--body); font-weight: 300; font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-3); }

.navlink {
  border-radius: var(--r-field); font-weight: 400; font-size: 14.5px;
  border: 1px solid transparent; position: relative;
}
.navlink:hover { background: var(--d-surface); color: var(--ink); }
.navlink.active {
  background: var(--d-raised); color: var(--ink); border-color: var(--d-hair);
  font-weight: 500;
}
/* aktiivisen merkki: spektrikaista, ei täyttöväri */
.navlink.active::before {
  content: ""; position: absolute; left: -1px; top: 10px; bottom: 10px;
  width: 2px; border-radius: var(--r-xs); background: var(--act);
}
.navlink.active .ic { color: var(--amb); }
.navlink .badge { background: var(--act); color: #1a1109; border: none; font-weight: 700; }

/* ---------------- Topbar ---------------- */
.topbar {
  /* Läpinäkymätön: yläpalkki on sticky, ja koska backdrop-filter on kielletty,
     puoliläpinäkyvä tausta jättäisi vieritetyn sisällön näkymään otsikon läpi. */
  background: var(--d-bg);
  border-bottom: 1px solid var(--d-hair);
  padding: 24px 40px;
}
.side { background: var(--d-bg); }
/* Otsikkohyppy: 12px label → 46px otsikko = lähes 4×. Vahvuus 300 (ääripää). */
.topbar h1 {
  font-family: var(--display); font-weight: 300;
  font-size: clamp(28px, 3.4vw, 46px); letter-spacing: -0.03em; line-height: 1.02;
}
.topbar .sub {
  font-family: var(--body); font-weight: 300; font-size: 14.5px;
  color: var(--ink-3); margin-top: 7px; letter-spacing: 0.01em;
}
.status-pill {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--data); border-color: rgba(52,194,174,0.30); background: rgba(52,194,174,0.09);
  padding: 8px 14px;
}
.status-pill.paused { color: var(--alert); border-color: rgba(246,169,97,0.32); background: rgba(246,169,97,0.10); }
.live-dot { background: var(--data); box-shadow: 0 0 10px rgba(52,194,174,0.9); }

/* ---------------- Yksi orkestroitu sisääntulo ----------------
   Ei siroteltuja mikroanimaatioita: yksi porrastettu nousu per näkymä. */
.view.active { animation: none; }
.view.active > * {
  opacity: 0; transform: translateY(14px);
  /* Mitattu 27.8.2026: kesto .62s + viimeinen viive .32s = 0,94s siihen
     asti kun viimeinen lohko on paikallaan. Craft-katto UI-animaatiolle
     on 0,30s. Uusi kokonaisaika 0,28 + 0,18 = 0,46s, ja porrastus 50ms
     pysyy craft-alueen sisalla (40-80ms), eli sisaantulo on yha
     orkestroitu eika yhtaikainen. */
  animation: dashIn .28s cubic-bezier(.2,.75,.2,1) both;
}
.view.active > *:nth-child(1) { animation-delay: .00s; }
.view.active > *:nth-child(2) { animation-delay: .05s; }
.view.active > *:nth-child(3) { animation-delay: .10s; }
.view.active > *:nth-child(4) { animation-delay: .15s; }
.view.active > *:nth-child(n+5) { animation-delay: .2s; }
@keyframes dashIn { to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .view.active > * { animation: none; opacity: 1; transform: none; }
}

/* ---------------- Paneelit ---------------- */
.panel {
  background: var(--d-surface);
  border: 1px solid var(--d-hair);
  border-radius: var(--r-panel);
  padding: 30px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
}
.panel-head h3 {
  font-family: var(--display); font-weight: 500; font-size: 21px; letter-spacing: -0.015em;
}
.eyebrow {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-3);
}
.eyebrow::before { background: var(--act); opacity: 1; width: 20px; height: 1.5px; }
.eyebrow.quiet::before { background: var(--d-hair-2); }

/* ---------------- Tunnusluvut: typografian ääripää ----------------
   11px label vastaan 88px numero = 8× hyppy. Numero vahvuudella 200. */
.stats { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 20px; }
.stat {
  background: var(--d-surface); border: 1px solid var(--d-hair);
  border-radius: var(--r-panel); padding: 26px 28px 24px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
  position: relative; overflow: hidden;
}
.stat::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(52,194,174,0.5), transparent);
}
.stat .label {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-3);
}
.stat .num {
  font-family: var(--display); font-weight: 200;
  font-size: clamp(52px, 6.5vw, 82px); line-height: 0.94;
  letter-spacing: -0.045em; margin-top: 16px; color: var(--ink);
  font-variant-numeric: tabular-nums;
  /* Yhden rivin korkeus varataan JO LATAUKSEN AIKANA.
     Mitattu 11.8.2026: luurankopalkki on 40 px ja valmis luku 77 px, joten
     kun luku saapui, laatikko kasvoi 37 px ja koko sivu hyppäsi alaspäin.
     Se on juuri se mitä luurangon on tarkoitus estää; luuranko oli siis
     oikea ratkaisu mutta vajaa. 0.94em = tasan sama kuin line-height, eli
     tämä pysyy oikeana myös kapealla ruudulla jossa font-size on pienempi.
     Älä vaihda pikseleiksi. */
  min-height: 0.94em;
}
/* Luurankopalkki keskelle varattua riviä. Sääntö on voimassa vain niin
   kauan kuin palkki on olemassa, joten valmis luku piirtyy täsmälleen
   kuten ennenkin. */
.stat .num:has(.lk-luku-iso) { display: flex; align-items: center; }
.stat .num.muted { color: var(--ink-3); opacity: 0.5; }
.stat .delta { font-family: var(--body); font-weight: 300; font-size: 13px; color: var(--ink-3); margin-top: 10px; }

/* ---------------- Napit: gradientti VAIN teolle ---------------- */
.btn { border-radius: var(--r-pill); font-weight: 600; letter-spacing: 0.005em; }
.btn-primary { background: var(--act); color: #1a1109; border: none; }
.btn-ghost, .btn:not(.btn-primary):not(.btn-danger) {
  background: var(--d-surface); border: 1px solid var(--d-hair-2); color: var(--ink);
}
.btn-ghost:hover, .btn:not(.btn-primary):not(.btn-danger):hover {
  background: var(--d-raised); border-color: rgba(155,124,240,0.45);
}

/* ---------------- Kentät ---------------- */
.input, .pill-input, textarea, select, .memory-edit, .draft-text {
  background: rgba(12,10,8,0.6); border-color: var(--d-hair-2); border-radius: var(--r-field);
}
.input:focus, textarea:focus, .draft-text:focus {
  border-color: rgba(155,124,240,0.6);
  box-shadow: 0 0 0 3px rgba(155,124,240,0.14);
}
/* Taukokytkin: app.css värjäsi PÄÄLLÄ-tilan amberiksi, mikä on sekä ruma että
   semanttisesti väärin päin (keltainen lupaa varoitusta, ei "kaikki hyvin").
   Töissä = teal (elossa), tauolla = amber (pysäytetty). */
.switch input:checked + .track { background: var(--data); }
.mark-status .switch.pause input:checked + .track { background: var(--data); }
.mark-status .switch.pause input:not(:checked) + .track { background: rgba(246,169,97,0.45); }
.mark-status .switch.pause input:not(:checked) + .track::after { background: var(--alert); }
.set-row { border-bottom-color: var(--d-hair); }
.set-row .info b { font-family: var(--body); font-weight: 600; font-size: 14.5px; }
.set-row .info p { font-weight: 300; }

/* ---------------- Tyhjät tilat ---------------- */
.empty h3 { font-family: var(--display); font-weight: 300; font-size: clamp(22px, 2.6vw, 30px); letter-spacing: -0.02em; }
.empty p { font-weight: 300; color: var(--ink-2); }
.empty .empty-icon {
  background: var(--d-surface); border: 1px solid var(--d-hair);
  border-radius: var(--r-panel); color: var(--amb);
}

/* ============================================================
   CHAT — "Puhu Markille"
   Tavoite: ei laatikkoon suljettua botti-ikkunaa vaan tila jossa
   Mark puhuu. Markin viesteillä EI ole kuplaa: ne ovat tekstiä
   valokaistan vieressä, koska kupla tekee tekoälystä sovelluksen
   ja kaista tekee siitä läsnäolon.
   ============================================================ */
#view-chat { max-width: none; padding: 0; }
.chat-shell { height: calc(100vh - 96px); }
.chat {
  background: transparent; border: none; border-radius: 0; box-shadow: none;
  position: relative;
}
/* Markin läsnäolokenttä: hengittävä hehku koko keskustelun takana */
.chat::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background: radial-gradient(60% 44% at 50% 108%, rgba(155,124,240,0.16) 0%, transparent 72%);
  animation: fieldBreath 9s ease-in-out infinite;
}
@keyframes fieldBreath { 0%,100% { opacity: .55; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .chat::before { animation: none; } }

/* z-index 5 eika 1. TODENNETTU SELAIMESSA 9.8.2026: .chat-head oli z-index 1
   ja loi pinoamiskontekstin, joten sen sisalla oleva keskustelulista jai
   arvoon 1 vaikka sille oli annettu 40. .chat-body on niin ikaan z-index 1 ja
   tulee DOM:ssa myohemmin, joten se piirtyi paalle. Lista NAKYI (taustat ovat
   lapinakyvia) mutta klikkaus osui .chat-empty-lohkoon, eika vanhoja
   keskusteluja paassyt avaamaan lainkaan. Mitattu elementFromPoint-kutsulla:
   ennen "DIV.chat-empty", jalkeen napin oma sisalto.
   ALA laske tata takaisin ykkoseen ilman etta testaat listan avaamisen. */
/* display:flex/align-items/gap eivat enaa tule app.cssn jaetusta
   .chat-head-saannosta (n. rivi 364) — se on kirjoitettu toiselle,
   vaalealle chat-kaytolle, ja piilevana riippuvuutena se oli S1-briefin
   (9.8.2026) vika 1:n juurisyy: .chat-meta sai app.cssista margin-left:auto
   ja tyontyi flexilla samaan 40px:n reunaan johon .chat-threads oli
   erikseen ankkuroitu absoluuttisesti, ja tekstit piirtyivat paallekkain.
   Dashboard.css omistaa nyt koko rivin eika luota siihen etta toinen
   tiedosto pysyy muuttumattomana. */
.chat-head {
  background: transparent; border-bottom: 1px solid var(--d-hair);
  padding: 20px 40px; position: relative; z-index: 5;
  display: flex; align-items: center; gap: 12px;
}
.chat-who { min-width: 0; }
.chat-who b { font-family: var(--display); font-weight: 500; font-size: 17px; letter-spacing: -0.01em; }
.chat-who span {
  font-family: var(--body); font-weight: 400; font-size: 11px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--data); gap: 7px;
}
.chat-head .chat-meta {
  font-family: var(--body); font-weight: 300; font-size: 11px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-3);
  margin-left: auto; flex: none; white-space: nowrap;
}

/* OPETUSTA ODOTTAA VAHVISTUSTA (2.9.2026, spec markin-yhtenainen-konteksti
   3.3.3, K1 nakyvaksi). Oma kapea rivi otsikon alla, ei osa .chat-head-
   flexia: nayttaa/piiloutuu JS:sta maaran mukaan eika saa tyontaa Markin
   nimea tai tilaa. Sama amber-savy kuin muualla "vaatii huomiota"
   (.hav.uusi, .hav-laji.laji-eskalaatio) — ei uusi vari, uusintakaytto. */
.chat-opetus-rivi {
  display: block; width: 100%; text-align: left; border: none;
  border-bottom: 1px solid var(--d-hair);
  background: rgba(246, 169, 97, 0.06);
  color: var(--ink-2); font-family: var(--body); font-size: 13px; line-height: 1.5;
  padding: 10px 40px; margin: 0; cursor: pointer; position: relative; z-index: 4;
  text-decoration: underline; text-underline-offset: 2px;
}
.chat-opetus-rivi:hover { color: var(--ink); }
@media (max-width: 560px) { .chat-opetus-rivi { padding: 10px 20px; } }

.chat-body {
  padding: 44px 40px 28px; gap: 34px; position: relative; z-index: 1;
  scroll-behavior: smooth;
}
.chat-body > * { width: 100%; max-width: 780px; margin-inline: auto; }

/* --- Markin viesti: ei kuplaa, vaan valokaista ---
   Leveys tulee .chat-body > * -lukupalstasta (780px), ei tästä:
   yli 100 merkin rivi on lukukelvoton riippumatta siitä miten kaunis se on. */
.msg.mark { align-self: stretch; position: relative; padding-left: 26px; }
.msg.mark::before {
  content: ""; position: absolute; left: 0; top: 4px; bottom: 4px; width: 2px;
  border-radius: var(--r-xs); background: var(--act);
  box-shadow: 0 0 14px rgba(185,139,230,0.55);
}
.msg.mark .who {
  font-family: var(--body); font-weight: 600; font-size: 11px;
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--amb); margin-bottom: 9px;
}
.msg.mark .bubble {
  background: none; border: none; padding: 0; border-radius: 0;
  font-family: var(--body); font-weight: 300; font-size: 17px; line-height: 1.62;
  color: var(--ink); letter-spacing: 0.002em;
}

/* --- Sinun viestisi: tiivis lasipilleri, oikeassa reunassa lukupalstaa --- */
.msg.you { align-self: stretch; text-align: right; }
.msg.you .bubble { max-width: 78%; }
.msg.you .who {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--ink-3); margin-bottom: 8px;
}
.msg.you .bubble {
  background: var(--d-raised); border: 1px solid var(--d-hair-2);
  color: var(--ink); font-weight: 400; font-size: 14.5px; line-height: 1.55;
  /* Kupla on tarkoituksella epasymmetrinen: nurkka lahettajan puolella on
     terava, mika kiinnittaa kuplan puhujaan. Arvot asteikosta. */
  padding: 14px 18px; border-radius: var(--r-panel) var(--r-panel) var(--r-xs) var(--r-panel);
}

/* --- Sanapaljastus: ajatus materialisoituu (sumeasta terävään) --- */
.msg.mark .bubble .w {
  display: inline-block; opacity: 0;
  transform: translateY(7px);
  filter: blur(7px);
  will-change: opacity, transform, filter;
}
.msg.mark .bubble.revealing .w {
  opacity: 1; transform: none; filter: blur(0);
  transition: opacity .4s cubic-bezier(.2,.8,.2,1),
              transform .4s cubic-bezier(.2,.8,.2,1),
              filter .4s cubic-bezier(.2,.8,.2,1);
  transition-delay: calc(var(--i) * 52ms);
}
@media (prefers-reduced-motion: reduce) {
  .msg.mark .bubble .w { filter: none !important; }
}

/* --- Mark miettii: ei laatikkoa, vaan pyyhkäisevä valo --- */
.thinking {
  background: none; border: none; padding: 0 0 0 26px; border-radius: 0;
  position: relative; align-self: stretch; gap: 14px;
  display: flex; align-items: center;
}
.thinking::before {
  content: ""; position: absolute; left: 0; top: 2px; bottom: 2px; width: 2px;
  border-radius: var(--r-xs); background: var(--amb); opacity: 0.35;
}
.thinking::after {
  content: ""; position: absolute; left: 0; width: 2px; height: 26px; border-radius: var(--r-xs);
  background: linear-gradient(180deg, transparent, #E9DDFF, transparent);
  box-shadow: 0 0 12px rgba(185,139,230,0.9);
  animation: scanRail 1.5s ease-in-out infinite;
}
@keyframes scanRail { 0% { top: 0; opacity: 0; } 20% { opacity: 1; } 80% { opacity: 1; } 100% { top: calc(100% - 26px); opacity: 0; } }
.thinking-label {
  font-family: var(--display); font-style: italic; font-weight: 300; font-size: 14.5px;
  color: var(--ink-2);
}
@media (prefers-reduced-motion: reduce) { .thinking::after { animation: none; opacity: 0; } }

/* --- Tyhjä tila --- */
.chat-empty { max-width: 34em; }
.chat-empty h3 {
  font-family: var(--display); font-weight: 300;
  font-size: clamp(26px, 3.4vw, 38px); letter-spacing: -0.028em; line-height: 1.06; margin-bottom: 14px;
}
.chat-empty p { font-weight: 300; font-size: 17px; color: var(--ink-2); }
.suggest-chip {
  background: var(--d-surface); border: 1px solid var(--d-hair-2); color: var(--ink-2);
  font-weight: 400; font-size: 14.5px; padding: 10px 16px;
}
.suggest-chip:hover {
  border-color: rgba(155,124,240,0.55); color: var(--ink);
  background: var(--d-raised); transform: translateY(-2px);
}

/* --- Composer: kelluva, spektrikehä fokuksessa --- */
.chat-input {
  background: transparent; border-top: none;
  padding: 10px 40px 30px; position: relative; z-index: 1;
  max-width: 860px; margin-inline: auto; width: 100%;
}
.chat-input::before {
  content: ""; position: absolute; inset: 10px 40px 30px; border-radius: var(--r-panel);
  background: rgba(12,10,8,0.82); border: 1px solid var(--d-hair-2);
  transition: border-color .2s ease, box-shadow .2s ease;
  pointer-events: none;
}
.chat-input:focus-within::before {
  border-color: rgba(155,124,240,0.5);
  box-shadow: 0 0 0 4px rgba(155,124,240,0.10), 0 18px 44px -22px rgba(0,0,0,0.9);
}
.chat-input textarea {
  background: transparent; border: none; border-radius: 0;
  font-weight: 300; font-size: 17px; padding: 18px 6px 18px 20px;
  position: relative; z-index: 1;
}
.chat-input textarea:focus { box-shadow: none; border: none; }
.chat-input textarea::placeholder { color: var(--ink-3); font-weight: 300; }
.chat-send {
  position: relative; z-index: 1; width: 42px; height: 42px; margin: 0 10px 9px 0;
  background: var(--act); color: #1a1109; border: none;
}
.chat-send:hover { transform: translateY(-1px) scale(1.04); }

/* --- Kuvanappi ja liitepalkki kelluvassa composerissa (12.8.2026) ---

   TAMA LOHKO ON PAKOLLINEN, ei viimeistelya. Composerin kentta ei ole
   tausta vaan .chat-input::before, absoluuttisesti sijoitettu lapsi.
   Sijoitettu elementti maalataan staattisten sisarustensa PAALLE, joten
   jokainen composerin kontrolli tarvitsee oman position+z-indexin tai se
   jaa kentan alle. .chat-send:lla se on (ylla). Kuvanapilta se puuttui,
   ja nappi jai 82-prosenttisesti peittavan kentan alle. Klikkaus meni
   silti lapi (::before on pointer-events:none), joten vika nakyi vain
   silmalle: se ei kaatanut mitaan, se vain nayttti rikkinaiselta.

   Marginaalit peilaavat lahetysnappia: se on 10px kentan oikeasta
   reunasta, tama 10px vasemmasta. Alamarginaali 8px asettaa 44px:n levyn
   keskiviivan tasan 42px:n lahetysnapin keskiviivalle (kentan sisapohja
   - 8 - 22 = kentan sisapohja - 9 - 21). */
.chat-liite-nappi {
  position: relative; z-index: 1;
  margin: 0 0 8px 10px;
  background: var(--d-raised); border-color: var(--d-hair-2);
}
.chat-liite-nappi:hover { background: var(--d-surface); border-color: rgba(155,124,240,0.55); color: var(--ink); }
.chat-liite-nappi:focus-visible,
.chat-input input[type="file"]:focus-visible + .chat-liite-nappi {
  outline: 2px solid var(--act-line); outline-offset: 2px;
}

/* Liitepalkki seuraa composerin geometriaa. Mitattu 12.8.2026: palkki oli
   koko chat-alueen levyinen (1176px) ja umpivarinen kaista, kun composer on
   keskitetty 860px:n kelluva pilleri. Ensimmainen liite alkoi 138px
   composerin vasemmasta reunasta vasemmalta, eli se ei nayttanyt kuuluvan
   siihen kenttaan jonka se tayttaa. 50px = composerin 40px + napin 10px,
   joten pikkukuvat alkavat tasan kuvanapin vasemmasta reunasta. */
.chat-liitteet {
  max-width: 860px; margin-inline: auto; width: 100%;
  padding: 8px 50px 0; background: transparent;
}
.chat-liite {
  background: var(--d-raised); border-color: var(--d-hair-2);
  border-radius: var(--r-field);
}
.chat-liite button:hover { background: var(--d-hair); color: var(--ink); }
.chat-liite button:focus-visible { outline-color: var(--act-line); }

.usage-line { max-width: 780px; margin-inline: auto; font-weight: 300; }

/* ============================================================
   NÄKYMÄKOHTAISET MUODOT
   Periaate sama kuin chatissa: jokainen näkymä saa muodon joka
   ilmaisee sen idean, ei geneeristä paneelia keskitetyllä
   tyhjällä tilalla.
   ============================================================ */

/* ---- Tilannerivi: ensimmäinen asia jonka omistaja näkee ----
   Ei tervehdys vaan tilanne. Sama hehkuva kaista kuin chatissa,
   jotta Markin ääni on tunnistettavasti sama koko tuotteessa. */
.statusline {
  display: grid; grid-template-columns: 2px 1fr; gap: 24px;
  padding: 30px 32px; margin-bottom: 22px;
  background: var(--d-surface); border: 1px solid var(--d-hair);
  border-radius: var(--r-panel);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
}
.statusline-rail { border-radius: var(--r-xs); background: var(--act); box-shadow: 0 0 14px rgba(185,139,230,0.5); }
.statusline-text {
  font-family: var(--display); font-weight: 300;
  font-size: clamp(19px, 2.1vw, 26px); line-height: 1.38;
  letter-spacing: -0.015em; color: var(--ink); margin-top: 4px; max-width: 46ch;
  /* Kaksi riviä varataan etukäteen. Latausteksti on yksirivinen ja lähes
     jokainen valmis tilanneteksti kaksirivinen, joten ilman tätä rivi
     kasvoi 33 px juuri silloin kun omistaja katsoo sitä. */
  min-height: 2.76em;
}
.statusline-text b { font-weight: 500; color: #fff; }
.statusline-actions { display: flex; gap: 12px; margin-top: 22px; flex-wrap: wrap; }
/* Napin paikanvaraaja. Tilannerivi saa napin lähes joka tilassa
   (renderStatusLine), ja se ilmestyi tyhjästä 2 s kohdalla työntäen kaiken
   alla olevan 37 px alas. renderStatusLine tyhjentää tämän lohkon ensi
   töikseen, joten paikanvaraaja katoaa itsestään eikä sitä tarvitse
   erikseen poistaa. */
.lk-nappi {
  display: inline-block; width: 168px; height: 37px; border-radius: var(--r-pill);
  background: var(--d-hair);
  animation: lkHengitys 1.8s ease-in-out infinite;
  will-change: opacity;
}
@media (prefers-reduced-motion: reduce) { .lk-nappi { animation: none; opacity: .55; } }
@media (max-width: 760px) { .lk-nappi { min-height: 44px; height: 44px; } }
.statusline.is-paused .statusline-rail { background: var(--alert); box-shadow: 0 0 14px rgba(246,169,97,0.5); }
.statusline.is-quiet .statusline-rail { background: var(--d-hair-2); box-shadow: none; }

/* ---- Työpäiväkirja: aikakaista, ei lista ----
   Rivit riippuvat yhdestä pystyviivasta, jolloin mitä tapahtui ja
   missä järjestyksessä on luettavissa yhdellä silmäyksellä. */
.journal-note { margin: -8px 0 22px; font-weight: 300; color: var(--ink-3); font-size: 13px; }
.journal { list-style: none; position: relative; padding-left: 26px; }
.journal::before {
  content: ""; position: absolute; left: 4px; top: 6px; bottom: 6px; width: 1px;
  background: linear-gradient(180deg, rgba(155,124,240,0.5), rgba(245,240,232,0.06));
}
.j-item { position: relative; padding: 16px 0; border-bottom: 1px solid var(--d-hair); }
.j-item:last-child { border-bottom: none; }
/* Solmu kertoo mitä tapahtui:
     täytetty teal + hehku = Mark toimi itsenäisesti
     teal rengas           = lähti sinun hyväksynnälläsi
     amber rengas          = odottaa sinua (ihmissignaali)
     rose rengas + hehku   = lähetys epäonnistui — tämä pitää huomata
     harmaa rengas         = hylkäsit, tai lähdössä
   Amber on varattu hetkille joissa ihmistä tarvitaan. Hylkäys ja
   epäonnistuminen eivät saa näyttää samalta: toisessa sinä päätit,
   toisessa tekniikka petti. */
.j-item::before {
  content: ""; position: absolute; left: -26px; top: 22px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--d-bg); border: 1px solid var(--alert);
}
.j-item.is-sent::before { border-color: var(--data); }
.j-item.is-sent.is-auto::before { background: var(--data); box-shadow: 0 0 9px rgba(52,194,174,0.7); }
.j-item.is-pending::before { border-color: var(--alert); }
.j-item.is-failed::before { border-color: #EC6FA0; box-shadow: 0 0 9px rgba(236,111,160,0.45); }
.j-item.is-rejected::before,
.j-item.is-sending::before,
.j-item.is-other::before { border-color: var(--ink-3); }
.j-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 5px; }
.j-time {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-3);
}
.j-state {
  font-family: var(--body); font-weight: 600; font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase;
  padding: 3px 9px; border-radius: var(--r-pill); border: 1px solid var(--d-hair-2); color: var(--ink-3);
}
.j-state.is-sent { color: var(--data); border-color: rgba(52,194,174,0.4); }
/* Amber, ei violettia: odottaminen on ihmishetki, ei Markin läsnäoloa. */
.j-state.is-pending { color: var(--alert); border-color: rgba(246,169,97,0.42); }
.j-state.is-failed { color: #EC6FA0; border-color: rgba(236,111,160,0.45); }
/* is-rejected ja is-sending jäävät vaimeaan perusilmeeseen tarkoituksella:
   hylkäys on sinun päätöksesi eikä poikkeama, ja lähdössä on ohimenevä. */
.j-text { font-weight: 300; font-size: 14.5px; line-height: 1.55; color: var(--ink-2); max-width: 62ch; }

/* ---- Mitä tarvitsen sinulta ---- */
.needs { list-style: none; }
.need {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 16px 0; border-bottom: 1px solid var(--d-hair); flex-wrap: wrap;
}
.need:last-child { border-bottom: none; }
.need p { font-weight: 300; font-size: 14.5px; color: var(--ink); margin: 0; }
.needs-clear { font-weight: 300; font-size: 14.5px; color: var(--ink-2); line-height: 1.6; max-width: 56ch; }

/* ---- Valtuusasteikko asetuksissa (sama kieli kuin myyntisivulla) ---- */
.scale-rule {
  position: relative; height: 2px; border-radius: var(--r-xs); margin: 4px 0 12px;
  background: linear-gradient(90deg, var(--data) 0%, var(--alert) 50%, #EC6FA0 100%);
}
.scale-rule .node {
  position: absolute; top: 50%; width: 13px; height: 13px; border-radius: 50%;
  transform: translate(-50%,-50%); border: 3px solid var(--d-bg);
}
.scale-rule .node-1 { left: 16.66%; background: var(--data); }
.scale-rule .node-2 { left: 50%; background: var(--alert); }
.scale-rule .node-3 { left: 83.33%; background: #EC6FA0; }
.scale-legend { display: flex; justify-content: space-between; margin-bottom: 22px; }
.scale-legend .lg {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.16em; text-transform: uppercase;
}
.lg-do { color: var(--data); } .lg-ask { color: var(--alert); } .lg-never { color: #EC6FA0; }

/* ============================================================
   NEUVOSTO
   ============================================================ */
#view-council { max-width: none; padding: 0; }
.council-shell {
  height: calc(100vh - 96px); display: flex; flex-direction: column;
  padding: 40px 40px 30px; overflow-y: auto; position: relative;
}
.council-empty { margin: auto; max-width: 40em; text-align: center; }
.council-empty h3 {
  font-family: var(--display); font-weight: 300;
  font-size: clamp(28px, 3.6vw, 42px); letter-spacing: -0.03em; line-height: 1.05; margin-bottom: 16px;
}
.council-empty p { font-weight: 300; font-size: 17px; line-height: 1.6; color: var(--ink-2); }
.council-empty .chat-suggest { margin-top: 26px; }

/* odotustila: neljä roolia vie oikeasti kymmeniä sekunteja */
.council-pending { margin: auto; text-align: center; max-width: 32em; }
.council-pending p { font-family: var(--display); font-style: italic; font-weight: 300; font-size: 17px; color: var(--ink-2); }
.council-scan {
  width: 190px; height: 2px; margin: 0 auto 22px; border-radius: var(--r-xs);
  background: rgba(245,240,232,0.10); position: relative; overflow: hidden;
}
.council-scan::after {
  content: ""; position: absolute; top: 0; left: -45%; width: 45%; height: 100%;
  background: linear-gradient(90deg, transparent, #B98BE6, transparent);
  animation: councilScan 1.5s ease-in-out infinite;
}
@keyframes councilScan { to { left: 100%; } }
@media (prefers-reduced-motion: reduce) { .council-scan::after { animation: none; } }

/* tulos: neljä ääntä omine väreineen, sitten ristiriidat ja suositus */
.council-result { max-width: 900px; margin: 0 auto; width: 100%; }
.council-result-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 26px;
  padding-bottom: 16px; border-bottom: 1px solid var(--d-hair);
}
.council-result-head h3 { font-family: var(--display); font-weight: 300; font-size: 30px; letter-spacing: -0.025em; }
.council-q {
  font-weight: 300; font-size: 14.5px; color: var(--ink-2); font-style: italic;
  margin-bottom: 26px; padding-left: 16px; border-left: 2px solid var(--d-hair-2); max-width: 62ch;
}
.voice-card { padding: 24px 0; border-top: 1px solid var(--d-hair); display: grid; grid-template-columns: 54px 1fr; gap: 20px; align-items: start; }
.voice-card .vn { font-family: var(--display); font-weight: 500; font-size: 30px; line-height: 1; }
.voice-card:nth-of-type(1) .vn { color: var(--alert); }
.voice-card:nth-of-type(2) .vn { color: #EC6FA0; }
.voice-card:nth-of-type(3) .vn { color: var(--amb); }
.voice-card:nth-of-type(4) .vn { color: var(--data); }
.voice-card h4 { font-family: var(--display); font-weight: 500; font-size: 21px; margin-bottom: 8px; letter-spacing: -0.01em; color: var(--ink); }
.voice-card p { font-weight: 300; font-size: 14.5px; line-height: 1.6; color: var(--ink-2); white-space: pre-wrap; }
.council-block { margin-top: 28px; padding-top: 22px; border-top: 1px solid var(--d-hair); }
.council-block h4 { font-family: var(--display); font-weight: 500; font-size: 21px; margin-bottom: 10px; color: var(--ink); }
.council-block p { font-weight: 300; font-size: 14.5px; line-height: 1.62; color: var(--ink-2); white-space: pre-wrap; }
.council-block.is-reco {
  background: var(--d-surface); border: 1px solid var(--d-hair);
  border-left: 2px solid #B98BE6;
  border-radius: 0 var(--r-panel) var(--r-panel) 0; padding: 24px 28px; margin-top: 26px;
}
.council-block.is-reco p { color: var(--ink); font-size: 17px; }
.council-raw { font-weight: 300; font-size: 14.5px; line-height: 1.62; color: var(--ink-2); white-space: pre-wrap; }
.council-foot {
  margin-top: 30px; padding-top: 18px; border-top: 1px solid var(--d-hair);
  font-weight: 300; font-size: 13px; line-height: 1.6; color: var(--ink-3); max-width: 62ch;
}
.council-foot .warn { display: block; margin-top: 8px; color: var(--alert); }

/* aiemmat arviot */
.council-history { max-width: 900px; margin: 34px auto 0; width: 100%; }
.council-hist-head { padding-bottom: 12px; border-bottom: 1px solid var(--d-hair); margin-bottom: 6px; }
.council-hist-item {
  display: flex; align-items: baseline; justify-content: space-between; gap: 20px;
  width: 100%; text-align: left; background: none; border: none; cursor: pointer;
  padding: 15px 4px; border-bottom: 1px solid var(--d-hair);
  transition: padding-left .2s cubic-bezier(.2,.8,.2,1), color .2s ease;
}
.council-hist-item:hover { padding-left: 10px; }
.council-hist-item .chq {
  font-weight: 300; font-size: 14.5px; color: var(--ink-2); line-height: 1.5;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.council-hist-item:hover .chq { color: var(--ink); }
.council-hist-item .cht {
  flex: none; font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3);
}

/* MARK HOITAA -nappirivi (jono #149, suunnitelma 2026-09-03-mark-yksi-
   kokonaisuus.md kohta 1.3.2). Uudelleenkäytettävä kontti niille kohteille
   joilla ei ollut valmista nappiriviä (esim. Neuvoston historiarivi, jossa
   koko rivi on jo oma <button>-elementtinsä eikä sen SISÄÄN voi laittaa
   toista nappia). Sama malli kuin .draft-actions/.bk-actions: flex+gap+
   margin-top, EI uusia värejä — napit käyttävät samaa .btn-tyyliä kuin
   "Hoida asia" muualla. */
.hoitaa-rivi { display: flex; align-items: center; gap: 10px; margin-top: 8px; padding: 0 4px; }

/* syöte */
.council-form {
  max-width: 900px; margin: 26px auto 0; width: 100%;
  background: rgba(12,10,8,0.82); border: 1px solid var(--d-hair-2); border-radius: var(--r-panel);
  padding: 6px 6px 6px 0; transition: border-color .2s ease, box-shadow .2s ease;
}
.council-form:focus-within { border-color: rgba(155,124,240,0.5); box-shadow: 0 0 0 4px rgba(155,124,240,0.10); }
.council-form textarea {
  width: 100%; resize: none; background: transparent; border: none; color: var(--ink);
  font-family: var(--body); font-weight: 300; font-size: 17px; line-height: 1.55;
  padding: 18px 20px 8px; min-height: 62px; max-height: 220px;
}
.council-form textarea:focus { outline: none; box-shadow: none; }
.council-form textarea::placeholder { color: var(--ink-3); font-weight: 300; }
.council-form-foot { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 0 12px 10px 20px; flex-wrap: wrap; }
.council-hint { font-weight: 300; font-size: 11px; color: var(--ink-3); max-width: 52ch; line-height: 1.5; }

/* ---------------- Työmäärä ----------------
   Määrä ilman hintaa. Palkki on suhteellinen eikä absoluuttinen: sen
   tehtävä on näyttää kanavien keskinäinen suhde yhdellä silmäyksellä,
   ei väittää mitään tavoitteesta. Asiakastyö saa värin, meidän kahden
   välinen työ jää harmaaksi — omistajan on nähtävä ero heti. */
.work-lead {
  font-family: var(--display); font-weight: 300; font-size: 21px;
  line-height: 1.5; color: var(--ink-2); max-width: 62ch;
}
.work-lead b { font-weight: 500; color: var(--ink); font-variant-numeric: tabular-nums; }
.work-sub { font-weight: 300; font-size: 13px; color: var(--ink-3); margin: 8px 0 26px; max-width: 62ch; }
.work-rows { list-style: none; display: grid; gap: 15px; }
.work-row { display: grid; grid-template-columns: minmax(130px, 200px) 1fr auto; align-items: center; gap: 18px; }
.work-name { font-weight: 300; font-size: 14.5px; color: var(--ink-2); }
.work-bar { height: 6px; border-radius: var(--r-pill); background: var(--d-hair); overflow: hidden; }
.work-bar i {
  display: block; height: 100%; border-radius: var(--r-pill);
  background: var(--data); box-shadow: 0 0 10px rgba(52,194,174,0.45);
  transform-origin: left; animation: workGrow 720ms cubic-bezier(.2,.7,.3,1) both;
}
.work-bar.is-own i { background: var(--d-hair-2); box-shadow: none; }
.work-num {
  font-family: var(--display); font-weight: 300; font-size: 21px; color: var(--ink);
  font-variant-numeric: tabular-nums; min-width: 3ch; text-align: right;
}
.work-hist {
  margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--d-hair);
  font-weight: 300; font-size: 13px; color: var(--ink-3);
}
@keyframes workGrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) { .work-bar i { animation: none; } }

/* ---------------- Responsiivinen ---------------- */
@media (max-width: 920px) {
  .work-row { grid-template-columns: minmax(96px, 1fr) 1.2fr auto; gap: 12px; }
  .work-lead { font-size: 17px; }
  /* .topbar on display:none tassa mediakyselyssa (rivi ~2145), joten sen
     padding oli kuollutta koodia. Poistettu 26.8.2026. */
  .chat-head, .chat-body { padding-left: 20px; padding-right: 20px; }
  .chat-input { padding-left: 16px; padding-right: 16px; }
  .chat-input::before { inset: 10px 16px 30px; }
  /* Liitepalkki seuraa kenttaa: 16px + napin 10px. Ilman tata pikkukuvat
     karkaavat 34px kentan reunasta oikealle kapealla naytolla. */
  .chat-liitteet { padding-left: 26px; padding-right: 26px; }
  .stat .num { font-size: clamp(46px, 14vw, 62px); }
  .council-shell { padding: 24px 20px; }
  .statusline { padding: 24px 22px; gap: 18px; }
  .voice-card { grid-template-columns: 40px 1fr; gap: 14px; }
}

/* ============================================================
   VARAUSKALENTERI (kuukausiruudukko)
   Perinteinen kuukausiruudukko: 7 saraketta (ma–su), 4–6 riviä.
   Päiväsolussa kompaktit pillit; klikkaus avaa päivän varaukset
   detaljinäkymään ruudukon alle. Kanava (verkko / Mark / halli)
   erotetaan reunavärillä + pillillä, ei pelkällä värillä.
   ============================================================ */
.bk-toolbar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 4px 0 16px;
}
.bk-nav { display: flex; align-items: center; gap: 8px; }
.bk-arrow { min-width: 38px; text-align: center; font-size: 17px; line-height: 1; }
.bk-monthlabel {
  font-weight: 500; font-size: 17px; color: var(--ink);
  min-width: 150px; text-align: center; letter-spacing: -0.01em; text-transform: capitalize;
}
.bk-summary { font-weight: 300; font-size: 14.5px; color: var(--data); }

.bk-weekdays {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px;
  margin-bottom: 6px;
}
.bk-weekdays span {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3);
  text-align: center; padding: 2px 0;
}
.bk-grid {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px;
}
.bk-cell {
  background: var(--d-surface); border: 1px solid var(--d-hair);
  border-radius: var(--r-field); padding: 7px 7px 8px; min-height: 92px;
  display: flex; flex-direction: column; gap: 4px; min-width: 0;
  text-align: left; font: inherit; color: inherit; cursor: pointer;
  transition: border-color .15s linear, background .15s linear;
}
.bk-cell:hover { border-color: var(--d-hair-2); }
.bk-cell.bk-out { opacity: 0.4; }
.bk-cell.bk-today { border-color: rgba(155,124,240,0.55); background: rgba(155,124,240,0.06); }
.bk-cell.bk-selected { border-color: var(--data); box-shadow: 0 0 0 1px var(--data) inset; }
.bk-daynum {
  font-family: var(--mono, ui-monospace, monospace); font-variant-numeric: tabular-nums;
  font-size: 13px; font-weight: 500; color: var(--ink-2); align-self: flex-start;
}
.bk-cell.bk-today .bk-daynum { color: var(--amb); }
.bk-pill {
  font-size: 11px; line-height: 1.3; color: var(--ink);
  background: var(--d-raised); border-left: 3px solid var(--d-hair-2);
  border-radius: var(--r-xs); padding: 2px 5px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.bk-more { font-size: 11px; color: var(--ink-3); padding-left: 3px; }

.bk-detail {
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--d-hair);
  display: flex; flex-direction: column; gap: 12px;
}
.bk-detail-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.bk-detail-head b { font-weight: 600; font-size: 17px; color: var(--ink); text-transform: capitalize; }

.bk-card {
  background: var(--d-raised); border: 1px solid var(--d-hair);
  border-left-width: 3px; border-radius: var(--r-field); padding: 8px 9px;
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
}
.bk-site-edge  { border-left-color: var(--data); }
.bk-mark-edge  { border-left-color: #EC7FA2; }
.bk-owner-edge { border-left-color: var(--amb); }
.bk-cancelled { opacity: 0.5; }

.bk-time {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  font-family: var(--mono, ui-monospace, monospace); font-variant-numeric: tabular-nums;
  font-size: 13px; color: var(--ink); flex-wrap: wrap;
}
.bk-name { font-weight: 500; font-size: 14.5px; color: var(--ink); word-break: break-word; }
.bk-meta { font-weight: 300; font-size: 13px; color: var(--ink-2); }
.bk-phone { font-size: 13px; color: var(--data); text-decoration: none; }
.bk-phone:hover { text-decoration: underline; }

.bk-chip {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.12em; text-transform: uppercase;
  padding: 2px 7px; border-radius: var(--r-pill); border: 1px solid var(--d-hair-2);
  color: var(--ink-3); white-space: nowrap;
}
.bk-chip.bk-site  { color: var(--data); border-color: rgba(52,194,174,0.4); }
.bk-chip.bk-mark  { color: #EC7FA2; border-color: rgba(236,127,162,0.4); }
.bk-chip.bk-owner { color: var(--amb); border-color: rgba(155,124,240,0.4); }

.bk-actions { display: flex; align-items: center; gap: 8px; margin-top: 4px; flex-wrap: wrap; }
.bk-cancel { padding: 4px 10px; font-size: 13px; }
.bk-cancel-armed { color: #EC6FA0; border-color: rgba(236,111,160,0.6); }
.bk-note { font-size: 11px; }

.bk-legend { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 16px; }

/* Kapeilla ruuduilla ruudukko säilyy 7 sarakkeena (kalenteri vaatii sen),
   mutta solut tiivistyvät ja pillit lyhenevät. Päivän varaukset luetaan
   detaljinäkymästä, joka on aina täysleveä ja luettava. */
@media (max-width: 920px) {
  .bk-grid, .bk-weekdays { gap: 4px; }
  .bk-cell { min-height: 64px; padding: 5px 4px; border-radius: var(--r-field); }
  .bk-pill { font-size: 11px; padding: 1px 3px; }
  .bk-weekdays span { font-size: 11px; letter-spacing: 0.08em; }
  .bk-daynum { font-size: 11px; }
  .bk-monthlabel { min-width: 120px; font-size: 14.5px; }
}

/* ============================================================
   TYÖPÄIVÄKIRJA (view-journal) — omistajan luottamuspinta
   Lisätty 2026-08-01. Perutaan poistamalla tämä lohko, näkymä
   dashboard.html:stä ja assets/journal-view.js.

   Muoto on aikakaista eikä taulukko: rivit riippuvat yhdestä
   pystyviivasta ja ryhmittyvät päivän alle, joten rivi kertoo
   tarinan päivästä eikä tietokannan sisällöstä.

   Mobiili on pohja: perussäännöt on kirjoitettu puhelimelle ja
   min-width vain laajentaa. Yrittäjä katsoo tämän illalla.

   Ei backdrop-filteriä, ei ulkoisia pyyntöjä, vain opacity- ja
   transform-animaatioita.
   ============================================================ */

/* ---- Yhteenvetorivi: sama muoto kuin Tilanne nyt -tilannerivi ---- */
.j-summary .statusline-text { max-width: 40ch; }
/* Odottavat asiat vaihtavat kaistan amberiin: ihmistä tarvitaan.
   Epäonnistunut lähetys vaihtaa sen roseen: se ei ole työjonoa vaan
   rikki, ja se voittaa amberin. */
.j-summary.is-wait .statusline-rail {
  background: var(--alert); box-shadow: 0 0 14px rgba(246,169,97,0.5);
}
.j-summary.is-fail .statusline-rail {
  background: #EC6FA0; box-shadow: 0 0 14px rgba(236,111,160,0.5);
}

/* ---- Katkos ei saa nayttaa tyhjyydelta ----
   .empty-inline ja .note piirretaan seka silloin kun mitaan ei ole
   tapahtunut etta silloin kun hakua ei saatu. Teksti erottaa ne, ulkoasu
   ei: molemmat ovat samaa harmaata. Mitattu 1.9.2026 sieppaamalla haut
   500:ksi — katkoksessa ja tyhjassa listassa oli sama merkkimaara ja
   sama ulkoasu, eli omistaja ei voi erottaa niita.
   Rose = rikki, sama saanto kuin .j-summary.is-fail yllä. Lyhyt kaista,
   ei vasenta reunaviivaa eika tayttovaria: kaista on talon tapa merkita
   tila, ja tayttovari tekisi virheesta korttimaisen koristeen.
   HUOM: odottaa `is-fail`-luokkaa JS-puolelta. Ilman sita tama on inertti. */
.empty-inline.is-fail,
.note.is-fail { color: var(--ink); }

.empty-inline.is-fail::before,
.note.is-fail::before {
  content: ""; display: block; width: 24px; height: 2px;
  border-radius: var(--r-xs); margin-bottom: 14px;
  background: #EC6FA0; box-shadow: 0 0 10px rgba(236,111,160,0.45);
}
/* .empty-inline on keskitetty, .note vasemmalle tasattu. Kaista seuraa. */
.empty-inline.is-fail::before { margin-left: auto; margin-right: auto; }

/* ---- Luvut OVAT suodattimet ----
   Erillinen lukurivi ja erillinen nappirivi sanoisivat saman asian
   kahdesti. Aktiivinen merkitään värikaistalla eikä täyttövärillä,
   samoin kuin sivupalkin aktiivinen kohta. */
.j-filters {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px; margin-bottom: 22px;
}
.j-filter {
  position: relative; overflow: hidden; cursor: pointer;
  display: flex; flex-direction: column; gap: 6px;
  min-height: 92px; padding: 15px 16px 14px;
  text-align: left; font: inherit; color: inherit;
  background: var(--d-surface); border: 1px solid var(--d-hair);
  border-radius: var(--r-panel);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
  transition: border-color .15s linear, background .15s linear;
}
.j-filter:hover { border-color: var(--d-hair-2); }
.j-filter:focus-visible { outline: 2px solid var(--amb); outline-offset: 2px; }
.j-filter.is-on { background: var(--d-raised); border-color: var(--d-hair-2); }

/* Typografian ääripää: 10px label vastaan ~48px luku. */
.j-filter .n {
  font-family: var(--display); font-weight: 200; line-height: 0.88;
  font-size: clamp(34px, 10vw, 48px); letter-spacing: -0.035em;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.j-filter .l {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-3);
}
/* Pieni piste sitoo suodattimen aikajanan solmuväreihin. */
.j-filter .l::before {
  content: ""; display: inline-block; width: 6px; height: 6px;
  border-radius: 50%; margin-right: 8px; vertical-align: 1px;
  background: var(--ink-3);
}
.j-filter.t-all .l::before { display: none; }
.j-filter.t-done   .l::before { background: var(--data); }
.j-filter.t-wait   .l::before { background: var(--alert); }
.j-filter.t-unsure .l::before { background: var(--amb); }

.j-filter::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; opacity: 0; background: var(--d-hair-2);
}
.j-filter.is-on::after { opacity: 1; }
.j-filter.t-all.is-on::after    { background: var(--act); }
.j-filter.t-done.is-on::after   { background: var(--data); }
.j-filter.t-wait.is-on::after   { background: var(--alert); }
.j-filter.t-unsure.is-on::after { background: var(--amb); }

@media (min-width: 720px) {
  .j-filters { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
  .j-filter { min-height: 104px; padding: 18px 20px 16px; }
  .j-filter .n { font-size: clamp(40px, 4.4vw, 54px); }
}

/* ---- Aikajana ----
   Kaista kulkee säiliön läpi, joten päiväotsikot ovat merkkejä
   janalla eivätkä katkaise sitä. */
.j-timeline { position: relative; padding-left: 26px; animation: jFade 220ms ease both; }
.j-timeline::before {
  content: ""; position: absolute; left: 4px; top: 10px; bottom: 10px; width: 1px;
  background: linear-gradient(180deg, rgba(155,124,240,0.5), rgba(245,240,232,0.06));
}
.j-timeline.is-empty::before { bottom: auto; height: 46px; }
.j-list { list-style: none; }

@keyframes jFade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .j-timeline { animation: none; } }

.j-day-label {
  display: flex; align-items: center; gap: 12px;
  margin: 26px 0 2px;
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-3);
}
.j-day-label:first-child { margin-top: 2px; }
.j-day-label::after { content: ""; flex: 1; height: 1px; background: var(--d-hair); }
.j-day-label span { white-space: nowrap; }

/* ---- Kanava ---- */
.j-chan {
  font-family: var(--body); font-weight: 400; font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3);
  opacity: 0.75;
}

/* ---- Markin oma huomio ----
   Epävarmuus ei ole vika vaan rehellisyys, joten se saa
   reunahuomautuksen muodon: sisennys, violetti hiusviiva ja
   kursiivi. Ainoa kursiivi listassa = löytyy silmäillen ilman
   varoitusväriä. */
.j-note {
  margin: 13px 0 2px; padding: 1px 0 1px 14px;
  border-left: 1px solid rgba(155,124,240,0.55);
  max-width: 62ch;
}
.j-note-label {
  display: block; margin-bottom: 5px;
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--amb);
}
.j-note p {
  font-family: var(--display); font-style: italic; font-weight: 400;
  font-size: 14.5px; line-height: 1.5; color: var(--ink-2); margin: 0;
}

/* ---- Siirtymä koko päiväkirjaan (Tilanne nyt -paneelista) ----
   Navigointia, ei toiminto: päiväkirja ei lähetä eikä hyväksy mitään. */
.j-more {
  display: inline-flex; align-items: center; gap: 7px;
  margin-top: 18px; padding: 6px 0; min-height: 34px;
  background: none; border: none; cursor: pointer;
  font-family: var(--body); font-weight: 500; font-size: 13px;
  letter-spacing: 0.02em; color: var(--ink-2);
}
.j-more i {
  font-style: normal; display: inline-block; will-change: transform;
  transition: transform .15s cubic-bezier(.2,.75,.2,1);
}
.j-more:hover { color: var(--ink); }
.j-more:hover i { transform: translateX(4px); }
.j-more:focus-visible { outline: 2px solid var(--amb); outline-offset: 3px; }

/* ---- 40 merkinnän katto: sanotaan ääneen, ei piiloteta ---- */
.j-cap {
  margin: 20px 0 0; padding-top: 16px; border-top: 1px solid var(--d-hair);
  font-weight: 300; font-size: 13px; color: var(--ink-3);
}

/* ---- Tyhjä tila ----
   Uuden asiakkaan ensivaikutelma. Kaista alkaa ja yksi ontto solmu
   odottaa ensimmäistä merkintää: tyhjä, ei rikki. */
.j-empty { position: relative; max-width: 62ch; padding-top: 2px; }
.j-empty::before {
  content: ""; position: absolute; left: -26px; top: 12px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--d-bg); border: 1px dashed var(--ink-3);
}
.j-empty h3 {
  font-family: var(--display); font-weight: 300;
  font-size: clamp(23px, 5.4vw, 30px); letter-spacing: -0.025em;
  line-height: 1.1; color: var(--ink); margin-bottom: 12px;
}
.j-empty p { font-weight: 300; font-size: 14.5px; line-height: 1.6; color: var(--ink-2); }
.j-empty .j-empty-2 { margin-top: 12px; color: var(--ink-3); font-size: 14.5px; }
.j-empty .btn { margin-top: 20px; }

.j-filtered-empty {
  font-family: var(--display); font-weight: 300; font-size: clamp(18px, 4.6vw, 22px);
  line-height: 1.4; letter-spacing: -0.015em; color: var(--ink-2); max-width: 46ch;
}

/* ---- Kapea ruutu ----
   Kaista kapenee mutta ei katoa: se tekee tästä päiväkirjan
   eikä listan. */
@media (max-width: 560px) {
  /* "Odottaa sinua" katkeaa kahdelle riville 10px/0.15em:llä. */
  .j-filter .l { font-size: 11px; letter-spacing: 0.1em; }
  .j-timeline { padding-left: 20px; }
  .j-timeline::before { left: 3px; }
  .j-item::before { left: -20px; top: 20px; width: 8px; height: 8px; }
  .j-empty::before { left: -20px; }
  .j-item { padding: 14px 0; }
  /* Aikaleima, tila ja kanava mahtuvat yhdelle riville 390px:llä.
     Pitkä kanavanimi saa yhä rivittyä — se rivittyy siististi
     tekstin vasempaan reunaan. */
  .j-head { gap: 8px; margin-bottom: 6px; }
  .j-state { font-size: 11px; letter-spacing: 0.1em; padding: 3px 7px; }
  .j-chan { font-size: 11px; letter-spacing: 0.1em; }
  .j-text { font-size: 14.5px; }
  .j-note { padding-left: 12px; }
  .j-note p { font-size: 14.5px; }
  .j-day-label { margin-top: 22px; letter-spacing: 0.12em; }
}

/* ============================================================
   ENSIMMÄINEN VIIKKO -POLKU (view-overview) — P3, 2026-08-02
   Perutaan poistamalla tämä lohko, #firstWeek dashboard.html:stä
   ja assets/firstweek-view.js.

   Muoto on sama aikakaista kuin työpäiväkirjassa: askeleet
   riippuvat yhdestä pystyviivasta, koska polku on lupaus siitä
   miltä ensimmäinen viikko näyttää — ja työpäiväkirja on se
   paikka jossa lupaus myöhemmin lunastetaan.

   Väriroolit (ei uusia värejä):
     teal    = tehty (data vahvisti)
     amber   = odottaa SINUA (ihmishetki, sama kuin päiväkirjassa)
     violetti= Mark odottaa maailmaa (ilmapiiri, katkoviiva)
     spektri = seuraava teko (sama kaista kuin toiminnoilla)

   Ei backdrop-filteriä, vain opacity/transform-animaatiot.
   ============================================================ */

.firstweek {
  background: var(--d-surface);
  border: 1px solid var(--d-hair);
  border-radius: var(--r-panel);
  padding: 30px 32px 14px;
  margin-bottom: 22px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
  position: relative;
  overflow: hidden;
}
/* Korkeusvaraus arvausta varten (jono #120, 2.9.2026, ks. iso kommentti
   firstweek-view.js:ssä FW_CACHE_KEY-lohkon yllä). Paneeli renderöidään
   heti localStorage-arvauksella (lyhyempi oletusteksti), ja pudotetaan
   pois vasta kun kaikki viisi kenttää ovat saapuneet — tämä varaa sen
   välisen erotuksen niin ettei lopullinen (pidempi) teksti KASVATA
   sivua enää sen jälkeen kun paneeli on jo näkyvissä. Luku on mitattu
   lopullinen korkeus + turvamarginaali; pudotus poistaa varauksen
   kokonaan, joten yritys jolla ei ole muokattavaa sivua ei jää
   roikkumaan turhan korkeana. */
.firstweek.is-settling { min-height: 860px; }
/* Hiljainen aurorajuova ylälaidassa: tämä lohko on tuotteen
   ensivaikutelma, joten se saa saman elävän ylärajan kuin
   tunnusluvut — mutta spektrillä, koska polku on tekoa. */
.firstweek::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px;
  background: linear-gradient(90deg, transparent 4%, rgba(247,179,107,0.55) 30%, rgba(236,127,162,0.55) 55%, rgba(185,139,230,0.55) 78%, transparent 96%);
}

.fw-head { margin-bottom: 6px; }
.fw-title-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 18px; flex-wrap: wrap; margin-top: 10px;
}
.fw-title {
  font-family: var(--display); font-weight: 300;
  /* Lattia 21px -> 24px (jono #121, 2.9.2026): .panel-head h3 on kiinteä
     21px KAIKKIALLA dashboardissa (15+ paneelia, app.css:380 +
     dashboard.css:164). Alle ~875px leveydellä clampin vw-osa painui
     lattialle ja h2/h3 renderöityivät samankokoisina — hierarkia katosi.
     Nosto koskee vain tätä yhtä h2:ta (käytössä kertaalleen,
     firstweek-view.js:267), ei .panel-head h3:aa jonka lasku olisi
     muuttanut kaikkia muita paneeleita. */
  font-size: clamp(24px, 2.4vw, 28px); letter-spacing: -0.02em; line-height: 1.2;
  color: var(--ink); margin: 0;
}
/* Eteneminen typografian ääripäänä: iso kevyt luku, pieni loppu. */
.fw-prog {
  font-family: var(--display); font-weight: 200; font-size: 14.5px;
  color: var(--ink-3); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.fw-prog b {
  font-weight: 200; font-size: 30px; line-height: 1; color: var(--ink);
  letter-spacing: -0.03em;
}
.fw-sub {
  font-weight: 300; font-size: 14.5px; line-height: 1.55; color: var(--ink-3);
  margin: 8px 0 0; max-width: 62ch;
}

/* ---- Askeleet yhdellä kaistalla ---- */
.fw-steps { list-style: none; margin: 18px 0 0; padding: 0; position: relative; }
.fw-steps::before {
  content: ""; position: absolute; left: 14px; top: 30px; bottom: 30px; width: 1px;
  background: linear-gradient(180deg, rgba(155,124,240,0.5), rgba(245,240,232,0.06));
}
.fw-item { border-bottom: 1px solid var(--d-hair); }
.fw-item:last-child { border-bottom: none; }

.fw-step {
  display: grid; grid-template-columns: 29px 1fr auto; gap: 17px;
  align-items: start; width: 100%;
  padding: 17px 0 16px; margin: 0;
  background: none; border: none; cursor: pointer;
  text-align: left; font: inherit; color: inherit; text-decoration: none;
  position: relative;
}
.fw-step:focus-visible { outline: 2px solid var(--amb); outline-offset: 3px; border-radius: var(--r-field); }

/* Solmu: numero kunnes data sanoo tehty. */
.fw-node {
  width: 29px; height: 29px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--d-bg); border: 1px solid var(--d-hair-2);
  position: relative; z-index: 1; margin-top: 1px;
}
.fw-node i {
  font-style: normal; font-family: var(--body); font-weight: 500;
  font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums;
}
.fw-step.is-done .fw-node { border-color: var(--data); background: rgba(52,194,174,0.10); }
.fw-step.is-done .fw-node i { color: var(--data); font-size: 13px; }
.fw-step.is-now .fw-node { border-color: rgba(246,169,97,0.6); box-shadow: 0 0 10px rgba(246,169,97,0.35); }
.fw-step.is-now .fw-node i { color: var(--alert); }
.fw-step.is-wait .fw-node { border-style: dashed; border-color: rgba(155,124,240,0.55); }
.fw-step.is-wait .fw-node i { color: var(--amb); }
.fw-step.is-next .fw-node { border-color: var(--d-hair-2); }
.fw-step.is-next .fw-node i { color: var(--ink); }

/* Teksti */
.fw-name {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-family: var(--body); font-weight: 500; font-size: 14.5px;
  letter-spacing: -0.005em; color: var(--ink); margin: 1px 0 0; line-height: 1.35;
}
.fw-step.is-done .fw-name { color: var(--ink-2); }
.fw-desc {
  font-weight: 300; font-size: 14.5px; line-height: 1.55;
  color: var(--ink-3); margin: 5px 0 0; max-width: 62ch;
}
.fw-step.is-now .fw-desc, .fw-step.is-next .fw-desc { color: var(--ink-2); }

/* Pillit: sama kieli kuin työpäiväkirjan tiloilla. Amber on ihmis-
   signaali, violetti kertoo että odotus ei ole omistajan vika. */
.fw-pill {
  font-family: var(--body); font-weight: 600; font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase;
  padding: 3px 9px; border-radius: var(--r-pill); border: 1px solid var(--d-hair-2); color: var(--ink-3);
}
.fw-pill.is-now { color: var(--alert); border-color: rgba(246,169,97,0.42); }
.fw-pill.is-wait { color: var(--amb); border-color: rgba(155,124,240,0.4); }

/* Uloskäynti: hiljainen nuolilinkki — paitsi seuraavalla teolla,
   joka saa spektrin (gradientti on varattu teolle). */
.fw-go {
  display: inline-flex; align-items: center; gap: 7px;
  align-self: center; white-space: nowrap;
  font-family: var(--body); font-weight: 500; font-size: 13px;
  letter-spacing: 0.02em; color: var(--ink-3);
}
.fw-go i {
  font-style: normal; display: inline-block; will-change: transform;
  transition: transform .15s cubic-bezier(.2,.75,.2,1);
}
.fw-step:hover .fw-go { color: var(--ink); }
.fw-step:hover .fw-go i { transform: translateX(4px); }
.fw-step.is-next .fw-go {
  background: var(--act); color: #1a1109; font-weight: 600;
  padding: 8px 16px; border-radius: var(--r-pill);
}
.fw-step.is-now .fw-go { color: var(--alert); }

/* Valmis: rauhallinen päätös, ei juhlaa. */
.firstweek.is-complete .fw-steps::before {
  background: linear-gradient(180deg, rgba(52,194,174,0.45), rgba(245,240,232,0.06));
}

/* ---- Kapea ruutu: solmu + teksti riviin, uloskäynti tekstin alle ---- */
@media (max-width: 640px) {
  .firstweek { padding: 24px 22px 10px; }
  .fw-step { grid-template-columns: 26px 1fr; gap: 14px; padding: 15px 0 14px; }
  .fw-steps::before { left: 12px; }
  .fw-node { width: 26px; height: 26px; }
  .fw-go { grid-column: 2; justify-self: start; margin-top: 9px; }
  .fw-prog b { font-size: 30px; }
  /* Sama lattia kuin ylla (21px -> 24px): tama saanto on myohemmin
     tiedostossa kuin .fw-title-perussaanto ja ylikirjoittaa clampin
     kokonaan alle 640px:n leveydella, joten pelkka clamp-muutos ei
     olisi korjannut mobiilia (390px oli juuri se leveys jolla vika
     mitattiin). */
  .fw-title { font-size: 24px; }
  /* Mobiilissa teksti rivittyy useammalle riville, joten varaus on
     isompi kuin desktopilla (ks. .firstweek.is-settling ylempänä).
     1440 eikä 1360: mitattu 2.9.2026 fixturella, arvaussisältö on 1426 px
     ja 1360 px:n varaus jätti 65 px:n kasvun fonttien vaihtuessa
     (firstweek-ei-hyppaa-test 9/10). Varaus saa olla yli, ei alle. */
  .firstweek.is-settling { min-height: 1440px; }
}


/* ===== TYOKALUALUEEN VALITSIN — POISTETTU 9.8.2026 =====
   Saannot .alue-valitsin / .alue-otsikko / .chat-alue / .alue-nappi /
   .alue-vihje poistettiin kun valitsin itse poistettiin markupista.

   Simo: "on liian iso ja vie chat ikkunalta liikaa tilaa... En ymmarra miksi
   kayttajan pitaa itse valita nappia painamalla mihin mark pystyy."

   Tyokalujoukon vaihtaa nyt Mark itse (mark-chat, TOOL_VAIHDA_ALUE), joten
   kayttoliittymassa ei ole enaa mitaan valittavaa. Chat-paneeli sai
   kehyksen viemat noin 90 px korkeutta takaisin viesteille. */

/* ============================================================
   KESKUSTELUT (säikeet). Simo 8.8.2026: "kyky aloittaa uusi keskustelu ja
   kohta jossa näet kaikki keskustelusi ja voit poistaa ne."

   Kaksi hillittyä nappia otsikkorivillä ja lista pudotuksena. Perustelu:
   chat-alue kuuluu kokonaan viesteille, ja pysyvä sivupalkki söisi siitä
   leveyttä sellaisen toiminnon takia jota käytetään harvoin. Kun listaa
   tarvitaan, se peittää hetkeksi ja katoaa.
   ============================================================ */
/* EI ENAA position:absolute. Vanha ratkaisu ankkuroi napit irti
   virtauksesta kohtaan right:40px — tasan samaan paikkaan johon
   .chat-meta tyontyy omalla margin-left:auto -saannollaan (katso
   .chat-head ylla). Kaksi eri asettelumekanismia tavoitteli samaa
   40px:n reunaa, ja tulos oli etta tekstit piirtyivat paallekkain
   ("napit ovat paallekkain", Simo 9.8.2026). .chat-threads on nyt
   tavallinen flex-lapsi .chat-head-rivilla, joten paallekkaisyys ei ole
   enaa geometrisesti mahdollinen: flexbox ei aseta kahta rivin lasta
   samaan tilaan. */
.chat-threads {
  flex: none;
  display: flex; align-items: center; gap: 6px;
}
/* flex: none + white-space: nowrap: nama napit ovat versaaleja ja
   harvennettuja, eli leveita suhteessa merkkimaaraan. Ilman naita ne
   kutistuvat ja teksti valuu naapurin paalle kapealla naytolla tai kun
   kirjasin latautuu eri levyisena kuin asettelussa oletettiin. */
.chat-tbtn {
  display: inline-flex; align-items: center; gap: 6px;
  flex: none; white-space: nowrap;
  font-family: var(--body); font-weight: 400; font-size: 11px;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
  padding: 6px 10px;
  background: transparent;
  border: 1px solid var(--d-hair);
  border-radius: var(--r-field);
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
.chat-tbtn:hover { color: var(--ink); border-color: var(--d-hair-2); }
.chat-tbtn:focus-visible { outline: 2px solid var(--act-line); outline-offset: 2px; }
.chat-tbtn svg { flex: none; opacity: 0.8; }

/* ---------- Keskitetty paneeli (Simon valinta 9.8.2026, variantti C) ----------

   Ei enaa pudotus. Perustelu: pudotus jai kelluvaksi chat-alueen paalle ja
   kilpaili viestien kanssa samasta tilasta. Keskustelujen hallinta on oma
   hetkensa, joten chat vaistyy taakse sen ajaksi ja palaa kun paneeli
   sulkeutuu.

   PANEELI JA PEITE OVAT <body>:n LAPSIA, eivat .chat-threadsin sisalla.
   Syy: .view.active > * saa animaation joka animoi transformia (dashIn,
   ks. "Yksi orkestroitu sisaantulo" ylla), ja transformoitu esi-isa on
   containing block myos position: fixedille. Chat-puun sisalla paneeli
   ankkuroituisi vaarin jos se avataan sisaantuloanimaation aikana.
   Bodyn lapsena yksikaan esi-isa ei voi vaikuttaa sijaintiin eika
   pinoamiseen. Tama komponentti on jo kerran kaatunut pinoamiskontekstiin
   (ks. z-index-kommentti .chat-headin kohdalla, 9.8.2026).
   ALA siirra paneelia takaisin chat-puun sisalle. */
.chat-lista-peite {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(9,7,5,0.74);
}
.chat-lista-peite[hidden] { display: none; }

.chat-lista {
  position: fixed; z-index: 61;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(380px, calc(100vw - 32px));
  max-height: min(78vh, 560px); overflow-y: auto;
  padding: 0;
  background: var(--d-modal);
  border: 1px solid var(--d-hair-2);
  border-radius: var(--r-panel);
  box-shadow: 0 40px 90px rgba(0,0,0,0.70);
}
.chat-lista-otsikko,
.chat-lista-tyhja,
.chat-lista-lataa {
  margin: 0; padding: 16px 18px 14px;
  border-bottom: 1px solid var(--d-hair);
  font-family: var(--body); font-weight: 400; font-size: 11px;
  letter-spacing: 0.18em; text-transform: uppercase;
  /* --ink-3 (#837B6E) jaa arvoon 4.42:1 tata taustaa vasten. Vaatimus on
     4.5:1. Tama savy on 4.93:1 eika eroa silmalla. Mitattu 9.8.2026. */
  color: #8B8376;
}
.chat-lista-tyhja,
.chat-lista-lataa { border-bottom: 0; }

.chat-lista-rivit { display: grid; gap: 0; padding: 8px; }
.chat-lista-rivi {
  position: relative;
  display: flex; align-items: center; gap: 6px;
  border-radius: var(--r-field);
}
.chat-lista-rivi:hover { background: var(--d-surface); }
/* Auki oleva keskustelu merkitaan viivalla eika pelkalla varilla (WCAG 1.4.1).
   Viiva on ::before eika box-shadow, koska --act on gradientti eika gradientti
   kelpaa box-shadow'n variksi: vanha saanto hylattiin aanettomasti ja viivaa
   ei ollut lainkaan. Sama gradienttiviiva merkitsee Markin viestin chatissa
   (.msg.mark::before), joten merkitys on sama kummassakin paikassa. */
.chat-lista-rivi.on { background: var(--d-surface); }
.chat-lista-rivi.on::before {
  content: ""; position: absolute; left: 0; top: 10px; bottom: 10px; width: 2px;
  border-radius: var(--r-xs); background: var(--act);
}

.chat-lista-avaa {
  flex: 1 1 auto; min-width: 0;
  display: grid; gap: 4px; text-align: left;
  padding: 13px 14px;
  background: transparent; border: 0; cursor: pointer;
}
/* color: var(--ink-1) oli kelvoton: sellaista tokenia ei ole (paletti on
   --ink, --ink-2, --ink-3). Nappi jai selaimen omaan mustaan, eli
   keskustelun nimen kontrasti oli 1.06:1 tummaa pohjaa vasten. Aikaleima
   nakyi mutta nimi ei. Mitattu 9.8.2026. */
.chat-lista-avaa b {
  font-family: var(--body); font-weight: 500; font-size: 14.5px; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-lista-avaa span {
  font-family: var(--body); font-weight: 300; font-size: 11px; color: #8B8376;
  font-variant-numeric: tabular-nums;
}
.chat-lista-avaa:focus-visible { outline: 2px solid var(--act-line); outline-offset: -2px; }

/* Poisto on lopullinen, joten se ei ole kuvake vaan sana, ja se vaatii
   toisen klikkauksen. Kuvakkeen merkitys arvataan, sanan ei. */
.chat-lista-poista {
  flex: none;
  font-family: var(--body); font-weight: 400; font-size: 11px;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: #8B8376;
  padding: 6px 9px; margin-right: 6px;
  background: transparent; border: 1px solid transparent;
  border-radius: var(--r-field); cursor: pointer;
}
.chat-lista-rivi:hover .chat-lista-poista { color: var(--ink); border-color: var(--d-hair-2); }
.chat-lista-poista.varmista {
  color: #FF9592; border-color: #FF9592; font-weight: 500;
}
.chat-lista-poista:disabled { opacity: 0.5; cursor: default; }
.chat-lista-poista:focus-visible { outline: 2px solid var(--act-line); outline-offset: 1px; }

@media (max-width: 760px) {
  /* "Yksityinen keskustelu" on koriste, ei toiminto: pois tieltä ennen
     kuin se joutuu tappelemaan tilasta nimen, tilan ja nappien kanssa. */
  .chat-head .chat-meta { display: none; }
  .chat-tbtn span { display: none; }
  .chat-tbtn { padding: 7px; }
}

/* ============================================================
   MYYNTIRAPORTTI (VK-F5b)

   Kolme lukua rinnakkain, sitten kaksi saraketta erittelya. Ei kaavioita:
   kolme lukua jotka pitavat paikkansa on parempi kuin pylvaat joita kukaan
   ei osaa lukea.

   VERTAILU EI OLE VAIN VARI (WCAG 1.4.1). Suunta luetaan tekstista
   ("+12 %", "-8 %"), ja vari on vain lisa. Nuolimerkki on mukana samasta
   syysta: se erottuu myos silloin kun varit eivat.
   ============================================================ */

.my-kpis {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  margin: 18px 0 4px;
}

.my-kpi {
  padding: 16px 18px 18px;
  background: var(--d-surface);
  border: 1px solid var(--d-hair);
  border-radius: var(--r-field);
  min-width: 0;
}

.my-kpi-nimi {
  display: block;
  font-family: var(--body);
  font-weight: 300;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* Luku on sivun suurin asia tassa paneelissa: se on se mita omistaja tuli
   katsomaan. Tabulaariset numerot pitavat kolme korttia samassa linjassa. */
.my-kpi-arvo {
  display: block;
  margin-top: 10px;
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(26px, 3.4vw, 38px);
  line-height: 1.05;
  letter-spacing: -0.035em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  overflow-wrap: anywhere;
}

.my-kpi-vertaa,
.my-kpi-tuleva {
  display: block;
  margin-top: 9px;
  font-family: var(--body);
  font-weight: 300;
  font-size: 13px;
  line-height: 1.35;
  color: var(--ink-3);
}

/* Suunta on jo tekstissa ("+12 %"). Nama lisaavat merkin ja savyn, eivat
   korvaa tekstia. Vihrea ja punainen EIVAT ole ainoa ero: nousussa on
   ylanuoli, laskussa alanuoli, tasan viiva. */
.my-kpi-vertaa.ylos  { color: var(--data); }
.my-kpi-vertaa.alas  { color: #E8A87C; }
.my-kpi-vertaa.tasan { color: var(--ink-3); }
.my-kpi-vertaa.ylos::before  { content: "\2191\00a0"; }
.my-kpi-vertaa.alas::before  { content: "\2193\00a0"; }
.my-kpi-vertaa.tasan::before { content: "\2013\00a0"; }

/* Tuleva myynti: sama koko kuin vertailu mutta neutraali savy. Tama EI ole
   hyva eika huono uutinen vaan tarkennus siihen mita luku sisaltaa. */
.my-kpi-tuleva {
  color: var(--ink-2);
  border-top: 1px solid var(--d-hair);
  padding-top: 8px;
}

/* ---- Kaksi saraketta: mista myynti tuli, miten varaus tehtiin ---- */
.my-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 26px;
  margin-top: 26px;
}
.my-col { min-width: 0; }

.my-otsikko {
  font-family: var(--body);
  font-weight: 400;
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 10px;
}

.my-rivi {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  padding: 11px 0;
  border-bottom: 1px solid var(--d-hair);
}
.my-rivi:last-child { border-bottom: none; }

.my-rivi-nimi {
  font-family: var(--body);
  font-weight: 400;
  font-size: 14.5px;
  color: var(--ink);
  min-width: 0;
  overflow-wrap: anywhere;
}

.my-rivi-oikea {
  display: flex;
  align-items: baseline;
  gap: 10px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.my-rivi-oikea b {
  font-family: var(--body);
  font-weight: 500;
  font-size: 14.5px;
  color: var(--ink);
}
.my-rivi-kpl {
  font-family: var(--body);
  font-weight: 300;
  font-size: 13px;
}

/* HUOMIO, EI VIRHE. Hinnoittelematon laji on rehellisyytta eika vika:
   raportti kertoo etta hinta puuttuu sen sijaan etta nayttaisi nollan ja
   vaikenisi. Siksi tassa EI ole varoitusvaria - sama savy kuin
   tyopaivakirjan epavarmuudella. */
.my-rivi-huomio {
  flex-basis: 100%;
  font-family: var(--body);
  font-weight: 300;
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink-3);
  margin-top: 2px;
  padding-left: 12px;
  border-left: 2px solid var(--d-hair-2);
}

/* Menetetty myynti: oma rivi lukujen alla, ei KPI. Se ei ole saavutus vaan
   tieto. */
.my-lost {
  margin-top: 22px;
  padding-top: 14px;
  border-top: 1px solid var(--d-hair);
  font-family: var(--body);
  font-weight: 300;
  font-size: 14.5px;
  color: var(--ink-2);
}
.my-lost b { font-weight: 500; color: var(--ink); }

@media (max-width: 860px) {
  .my-kpis { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .my-cols { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .my-kpi-arvo { font-size: 30px; }
}

/* Tyhja jakso: sama savy kuin muut selittavat tekstit. Ei varoitusta -
   tyhja kuukausi ei ole vika vaan tila. */
.my-tyhja {
  margin: 0;
  font-family: var(--body);
  font-weight: 300;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--ink-3);
}

/* ============================================================
   SIVUN MUUTOKSET (2026-08-11)

   Koti Markin sivupäivityksille. Rivi lainaa myyntiraportin
   rakennetta (.my-rivi-tyylinen otsikko + tunnisteet) ja
   työpäiväkirjan aikaleimakieltä. Ei uutta värikieltä: "Sivulla nyt"
   -sarake saa datan värin (teal) koska se ON se mikä juuri nyt elää
   asiakkaan sivulla, "Oli aiemmin" pysyy vaimeana.
   ============================================================ */
.sm-list { list-style: none; }
.sm-item { padding: 18px 0; border-bottom: 1px solid var(--d-hair); }
.sm-item:last-child { border-bottom: none; }

.sm-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.sm-time {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-3);
}
.sm-page {
  font-family: var(--body); font-weight: 600; font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase;
  padding: 3px 9px; border-radius: var(--r-pill); border: 1px solid var(--d-hair-2); color: var(--ink-3);
}
.sm-field { font-family: var(--body); font-weight: 500; font-size: 14.5px; color: var(--ink); }

.sm-diff { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.sm-col {
  min-width: 0; padding: 12px 14px;
  background: var(--d-surface); border: 1px solid var(--d-hair); border-radius: var(--r-field);
}
.sm-col-label {
  display: block; font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3); margin-bottom: 6px;
}
/* "Sivulla nyt" on se mikä elää juuri nyt asiakkaan sivulla — sama
   rooli kuin datalla muualla dashboardissa (teal, ei ilmapiiriväri). */
.sm-col-new { border-color: rgba(52,194,174,0.32); box-shadow: inset 2px 0 0 var(--data); }
.sm-col-new .sm-col-label { color: var(--data); }
.sm-col-old { opacity: 0.86; }
.sm-text { margin: 0; font-weight: 300; font-size: 14.5px; line-height: 1.5; color: var(--ink-2); overflow-wrap: anywhere; white-space: pre-wrap; }

.sm-foot { display: flex; align-items: center; gap: 14px; margin-top: 12px; flex-wrap: wrap; }
.sm-note { font-weight: 300; font-size: 13px; color: var(--ink-3); }
.sm-cap { margin-top: 14px; font-weight: 300; font-size: 13px; color: var(--ink-3); }

@media (max-width: 719.98px) {
  .sm-diff { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   VIIMEISTELYKERROS 2026-08-11
   Tietoisesti tiedoston LOPUSSA, samoin kuin app.css:n teemakerros:
   nama yliajavat kaskadilla eika yhtakaan olemassa olevaa saantoa
   tarvinnut purkaa. Perutaan poistamalla tama lohko.

   Ei uutta ilmetta. Jokainen saanto korjaa mitatun puutteen.
   ============================================================ */

/* ---- Ruudunlukijan teksti ilman nakyvaa tekstia ----
   Tarvitaan luurangoille: silma nakee palkit, ruudunlukija kuulee
   "Haetaan merkintoja". clip-path yksin ei riita Safarissa, siksi
   vanha clip-resepti mukana. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}

/* ---- LATAUTUMISTILA: luuranko, ei keskitettya tekstirivia ----
   Tama on se tila jonka asiakas nakee joka ainoa kerta kun han avaa
   sivun. Aiemmin paneelin keskella luki yksi harmaa rivi tyhjan
   paalla, ja sivu nayttti rikkinaiselta juuri silla sekunnilla kun
   siihen katsotaan ensimmaisena.

   Palkit noudattavat sen sisallon muotoa joka on tulossa, joten
   asettelu ei hyppaa kun data saapuu.

   Hengitys opacityllä, EI pyyhkaisygradienttia: se on sama liikekieli
   kuin fieldBreath ja thinkBreath, ja se on halpa (ei maalaa
   taustakuvaa uudelleen joka ruudulla). */
.laataus { display: grid; gap: 20px; padding: 4px 0 2px; }
.laataus .lk-rivi { display: grid; gap: 9px; }
.laataus .lk {
  display: block; height: 10px; border-radius: var(--r-pill);
  background: var(--d-hair);
  animation: lkHengitys 1.8s ease-in-out infinite;
  will-change: opacity;
}
/* Porrastus riveittain: yhta tahtia sykkiva luuranko nayttaa
   latausanimaatiolta, porrastettu nayttaa sisallolta joka saapuu. */
.laataus .lk-rivi:nth-child(2) .lk { animation-delay: .14s; }
.laataus .lk-rivi:nth-child(3) .lk { animation-delay: .28s; }
.laataus .lk-rivi:nth-child(4) .lk { animation-delay: .4s; }
/* Ensimmainen palkki rivilla on aikaleima -> vaimeampi, kuten oikeakin. */
.laataus .lk-rivi .lk:first-child { height: 8px; opacity: .8; }

.lk-w22 { width: 22%; } .lk-w24 { width: 24%; } .lk-w26 { width: 26%; }
.lk-w42 { width: 42%; } .lk-w48 { width: 48%; } .lk-w52 { width: 52%; }
.lk-w64 { width: 64%; } .lk-w70 { width: 70%; } .lk-w78 { width: 78%; }
.lk-w84 { width: 84%; } .lk-w88 { width: 88%; } .lk-w92 { width: 92%; }

/* Tyopaivakirjan luuranko istuu samaan kaistaan kuin oikeat merkinnat. */
/* LUURANKO VASTAA SEN SISALLON MITTAA JOTA SE LUPAA (loydos 8, 26.8.2026).
   Mitattu: luurankorivi oli 46 px, mutta oikea .j-item on mediaaniltaan
   172,9 px (arvot 150,5 / 172,9 / 298,5). Luuranko oli siis 3,8 kertaa
   liian matala, ja siita seurasi ettei se varannut tilaa vaan lupasi
   vaaraa: sivu kasvoi 1537 -> 4068 px ja alin paneeli liikkui 2180 px
   alaspain 390 px:n leveydella. Jos omistaja on ehtinyt osoittaa nappia,
   kohde karkaa.
   150 px on mitattu alaraja eika mediaani: luuranko saa luvata vahemman
   kuin sisalto tuo, mutta ei enempaa, muuten tyhja tila jaa nakyviin
   silloin kun merkintoja on vahan. align-content:start pitaa palkit
   rivin ylareunassa, jolloin kasvava korkeus nayttaa varatulta tilalta
   eika venyneelta palkilta. */
.laataus-jana .lk-rivi { min-height: 130px; align-content: start; }
.laataus-jana { padding-left: 26px; position: relative; }

/* Sama korjaus #needsList-luurangolle. Sen rivi oli ~19 px kun oikea
   sisalto on 294,9 px, joten sailio kasvoi 42 -> 295 px. Luurangolla ei ole
   omaa luokkaa, joten se kohdistetaan sailion kautta. 100 px on tarkoituksella
   alle mitatun: luuranko lupaa vahemman kuin sisalto tuo. */
#needsList .laataus .lk-rivi { min-height: 55px; align-content: start; }

/* Kapealla naytolla sama sisalto on korkeampaa, koska teksti kietoutuu
   useammalle riville. Yksi luurankoarvo ei siis palvele molempia: 1440 px:lla
   optimoitu arvo alittaa mobiilin sisallon reilusti. Kaytetaan SAMAA 920 px:n
   rajaa jolla layout muutenkin vaihtuu, jotta katkaisupisteita ei tule lisaa.
   Mitattu 390 px:lla: .j-item-lista 641px, #needsList 295px. */
@media (max-width: 920px) {
  .laataus-jana .lk-rivi        { min-height: 190px; }
  #needsList .laataus .lk-rivi  { min-height: 100px; }
}
.laataus-jana::before {
  content: ""; position: absolute; left: 4px; top: 6px; bottom: 6px; width: 1px;
  background: linear-gradient(180deg, rgba(155,124,240,0.35), rgba(245,240,232,0.05));
}

/* Tyomaaran luuranko noudattaa .work-row'n saraketta.
   min-height sailiolle eika riville: luurankorivien MAARA on kiinteasti
   kaksi HTML:ssa, mutta oikeita kanavarivaja voi olla enemman. Rivin
   korkeuden kasvattaminen tekisi luurangosta oikeaa lihavamman, joten tila
   varataan sailion tasolla ja rivit pysyvat .work-row'n mittaisina.

   ARVO ON ALARAJA EIKA KESKIARVO (korjattu 1.9.2026). Ensimmainen arvoni
   200px mitoitettiin yhdesta mittauksesta jossa kanavarivia oli nelja
   (sisalto 279px). Seuraavassa ajossa rivia oli kaksi (145px) ja luuranko
   YLITTI sisallon 55 pikselilla, eli sivu kutistui latauksen jalkeen.
   Kanavien maara riippuu datasta eika sita voi tietaa etukateen, joten
   mitoitus tehdaan pienimman todennakoisen sisallon mukaan. Luuranko saa
   luvata vahemman kuin sisalto tuo, ei enempaa. */
.laataus-palkit { gap: 15px; min-height: 140px; }
/* ...mutta ei sen KORKEUTTA (loydos 8, mitattu 26.8.2026). Luurankorivi oli
   10 px kun oikea .work-row on 51 px, joten sailio kasvoi 41 -> 357 px.
   Sarakkeet olivat oikein, korkeus ei. */
.laataus-palkit .lk-rivi {
  grid-template-columns: minmax(130px, 200px) 1fr auto;
  align-items: center; gap: 18px;
  min-height: 51px;
}
.laataus-palkit .lk-taysi { width: 100%; height: 6px; }
.laataus-palkit .lk-luku { width: 34px; }
.laataus-palkit .lk-rivi .lk:first-child { height: 10px; opacity: 1; }

/* Tunnusluvun luuranko. .lk on muualla .laataus-lapsi, tama elaa yksin
   .num-laatikon sisalla, joten hengitys maritellaan tassa uudelleen. */
.lk-luku-iso {
  display: block; width: 62px; height: 40px; border-radius: var(--r-field);
  background: var(--d-hair);
  animation: lkHengitys 1.8s ease-in-out infinite;
  will-change: opacity;
}
@media (prefers-reduced-motion: reduce) { .lk-luku-iso { animation: none; opacity: .55; } }

/* ---- Tyhja eyebrow ei piirra kaistaansa ----
   #workStamp, #bkStamp, #myStamp ja #memoryStamp ovat tyhjia kunnes data
   saapuu, mutta .eyebrow::before piirsi silti 20px:n vaakaviivan. Paneelin
   oikeaan ylakulmaan jai roikkumaan viiva jolla ei ollut tekstia. */
.eyebrow:empty::before { display: none; }

@keyframes lkHengitys { 0%, 100% { opacity: .40; } 50% { opacity: .95; } }
@media (prefers-reduced-motion: reduce) {
  .laataus .lk { animation: none; opacity: .55; }
}

@media (max-width: 920px) {
  .laataus-palkit .lk-rivi { grid-template-columns: minmax(96px, 1fr) 1.2fr auto; gap: 12px; }
}

/* ---- Hyppylinkin kohde ----
   #sisalto saa fokuksen vain ohjelmallisesti (tabindex="-1"), joten
   siihen ei kuulu fokusrengasta: rengas ilmestyisi hyppyyn eika
   nappaimistonavigointiin. */
#sisalto:focus { outline: none; }

/* ---- FOKUSRENGAS: yksi vari koko paneelissa ----
   Mitattu 11.8.2026: rengas oli teal (app.css, --warm) kaikkialla
   paitsi neljassa dashboardin omassa saannossa joissa se oli
   violetti (--amb). Kaksi eri fokusidentiteettia samalla sivulla.

   Oikea vari on --act-line, ei teal: teal on tassa jarjestelmassa
   DATAN vari (mitattu, vahvistettu), ja fokus ei ole dataa vaan
   tekoa. --act-line on jo tiedoston alussa maritelty nimenomaan
   tahan kayttoon ("korostuksen viivaksi, reunaksi tai
   fokusrenkaaksi"). Asetetaan vain outline-color, jolloin kunkin
   saannon oma leveys ja offset sailyvat. */
a:focus-visible, button:focus-visible, input:focus-visible,
textarea:focus-visible, select:focus-visible,
.navlink:focus-visible, .suggest-chip:focus-visible { outline-color: var(--act-line); }
.j-filter:focus-visible, .j-more:focus-visible, .fw-step:focus-visible { outline-color: var(--act-line); }

/* ---- Ensisijainen nappi: gradientti ei vaihda identiteettia hoverissa ----
   Mitattu 11.8.2026. Lepotilassa --act (amber -> rose -> violetti,
   3 pysakkia). Hoverissa app.css:n .btn-primary:hover (sama
   spesifisyys, aiempi tiedosto) vaihtoi sen --grad:iin, jossa on
   NELJAS pysakki: teal. Nappi siis kasvatti tealin hannan aina kun
   hiiri kavi sen paalla.

   Se rikkoi taman tiedoston oman saannon: spektri on varattu teolle
   ja teal on datan vari. Hoverpalaute tulee nyt kirkkaudesta ja
   nostosta, eli muodosta jota kaikki muutkin napit kayttavat. */
.btn-primary:hover {
  background: var(--act);
  filter: brightness(1.07) saturate(1.04);
  box-shadow: 0 18px 38px -14px rgba(185,139,230,0.55);
}
.btn-primary:active { filter: none; }

/* ---- Kosketuskohteet ----
   Mitattu: .btn-sm 37px, .j-more 34px, .chat-tbtn ~31px korkeita.
   44px on kosketusvaatimus, EI hiirivaatimus, joten tyonnetaan se
   vain kosketuslaitteille. Tyopoydan tiheys sailyy ennallaan. */
@media (pointer: coarse) {
  .btn-sm, .bk-arrow, .bk-cancel, .chat-tbtn, .j-more, .suggest-chip { min-height: 44px; }
  .bk-arrow, .chat-tbtn { min-width: 44px; }
  .side .brand { min-height: 44px; }
  /* LISAYS 31.8.2026: .switch on inline-flex ilman omaa korkeutta, joten
     se kutistui radan mittaiseksi. Mitattu kosketusemuloinnilla
     (pointer: coarse tosi): label.switch.pause 44 x 25 px. Se on tuotteen
     nakyvin ja vaarallisin kytkin (pysayttaa Markin) ja se on joka
     nakymassa. Rata pysyy visuaalisesti 25px:na, vain kosketusalue kasvaa. */
  .switch { min-height: 44px; }
}
/* .bk-cell jaa tarkoituksella alle 44px:n LEVEYDEKSI kapealla naytolla:
   390px / 7 saraketta = 37px, eika kuukausiruudukko voi olla kuin
   seitsemansarakkeinen. Solun korkeus on 64-119px, joten kosketuspinta-ala
   riittaa. Sama perustelu on jo kirjattu .bk-gridin media-lohkoon. */

/* ---- Kuukausinuolet ----
   Tekstimerkit ‹ › vaihdettiin SVG-chevroneiksi: ne olivat sivun
   ainoat tekstikuvakkeet keskella SVG-ikonistoa, ja kaksoiskoodaus
   oli tehnyt niista lukukelvottomia ("‹"). */
.bk-arrow { padding-inline: 12px; }
.bk-arrow svg { display: block; }

/* ---- Markin muisti ----
   .memory-view kirjoitettiin vaalealle pohjalle: taustaksi jai
   rgba(0,0,0,0.015), joka on tummalla pinnalla nakymaton, ja mitat
   olivat rem-asteikolla kun koko muu paneeli on px-asteikolla.
   Sama pinta kuin muillakin kentilla. */
.memory-view {
  background: var(--d-surface);
  border-color: var(--d-hair);
  border-radius: var(--r-field);
  font-size: 14.5px; line-height: 1.62;
  color: var(--ink-2);
}

/* ---- Kapea naytto: navigaatio kertoo etta se jatkuu ----
   Kapealla naytolla .side on vaakasuunnassa vierriva rivi (app.css,
   max-width: 920px). Nakyvissa on kaksi kohtaa kymmenesta, eika mikaan
   kerro etta loput kuusi ovat olemassa: reuna katkaisee kohdan siististi
   juuri niin etta se nayttaa listan lopulta.

   Hivytys on affordanssi, ei koriste: katkaistu kohta nayttaa lopulta,
   haipyva kohta nayttaa jatkuvalta. Ei muuta toimintaa eika asettelua.

   HUOM koordinaattorille: .burger on jo kytketty (dashboard.js) ja
   .side.mobile-open on jo tyylitelty (app.css), mutta .burger on
   display:none KAIKILLA leveyksilla, joten valmis valikko on pois
   paalta. Sen kytkeminen on toimintamuutos, ei viimeistelya. */
@media (max-width: 920px) {
  .side {
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 38px), transparent 100%);
    mask-image: linear-gradient(90deg, #000 calc(100% - 38px), transparent 100%);
  }
  .side.mobile-open { -webkit-mask-image: none; mask-image: none; }
}

/* ---- Pitka yrityksen nimi ----
   Mitattu: yhdyssanapitka nimi (esim. "Ravintolakokonaisuus...")
   leikkautui sivupalkissa, koska katkaisukohtaa ei ollut. */
/* Yrityksen nimi ja kayttajatunnus ovat vaihtuvamittaisia ja tulevat
   palvelimelta. Aiempi `overflow-wrap: anywhere` esti ylivuodon mutta
   salli RIVITTYMISEN, ja juuri se rikkoi valikon:

     lataushetkella lukee "Yrityksesi" (yksi rivi, 57 px), ja kun oikea
     nimi saapuu se rivittyy kahdelle riville (78 px) -> koko alempi
     valikko liukuu 21 px alas. Kayttaja joka klikkaa juuri silloin osuu
     vaaraan riviin.

   Mitattu 14.8.2026 hidastamalla kantakutsut: 725 px -> 746 px. Sama
   vikaluokka kuin varauskirja-linkin ilmestyminen, joka korjattiin
   varaamalla paikka (.vk-varaus) — mutta tata lohkoa ei huomattu silloin.

   Yksi rivi ja kolme pistetta: korkeus on vakio nimen pituudesta
   riippumatta, eika mikaan voi liikkua. Koko nimi on `title`-attribuutissa
   (asetetaan auth-supabase.js:ssa), joten mitaan ei katoa. */
.side .biz b,
.side .biz span {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}


/* ============================================================
   VIIMEISTELYKERROS II — 11.8.2026 ilta
   Ei uutta ilmetta: varit, typografia ja rakenne ennallaan.
   Jokainen saanto korjaa selaimessa mitatun puutteen, ja mittaus
   on kirjattu saannon viereen. Perutaan poistamalla tama lohko.
   ============================================================ */

/* ---- .quiet oli luokka jolla ei ollut yhtaan saantoa ----
   MITATTU 11.8.2026: .quiet esiintyy dashboardissa 14 kertaa, mutta
   CSS tunsi vain yhdistelman .eyebrow.quiet. Yksin kaytettyna luokka
   ei tehnyt MITAAN, joten teksti jai bodyn --ink-variin: kontrasti
   16.6:1, eli tasan yhta kirkas kuin otsikot.

   Nakyvin seuraus oli hierarkian kaantyminen ylosalaisin:
     .k-sub  "Mark on jasentanyt namat kertomastasi" paistoi
             kirkkaampana kuin otsikko jota se selittaa.
     .my-rivi-kpl "17 varausta" oli yhta vahva kuin euromaara
             sen vieressa, vaikka se on tarkennus lukuun.
     #notifySaved ja #settingsSaved (kuittaukset) huusivat
             kovempaa kuin asetus jota ne kuittaavat.

   --ink-2 eika --ink-3: nama kentat kantavat myos virheilmoituksia
   ("Lahetys epaonnistui"), ja virheen on oltava luettava. --ink-2 on
   selvasti hiljaisempi kuin --ink mutta ei rajatapaus kontrastissa.
   .eyebrow.quiet (0,2,0) voittaa taman ja pysyy --ink-3:na. */
.quiet { color: var(--ink-2); }
/* Yksi paikka jossa .quiet ei saa voittaa. .my-kpi-vertaa on jo asetettu
   --ink-3:ksi tarkoituksella: KPI-kortissa "edellinen jakso" on
   hiljaisempi kuin sen alla oleva "josta viela tulossa" (--ink-2), ja
   yleinen .quiet-saanto litisti eron pois. Mitattu ero ennen/jalkeen:
   rgb(131,123,110) -> rgb(188,180,167).
   :not()-ehdot ovat pakolliset: ilman niita tama saanto (0,2,0) voittaisi
   myohempana .ylos/.alas/.tasan -saannot (myos 0,2,0) ja tappaisi
   suuntaa kertovan varin, joka on koko rivin idea. */
.my-kpi-vertaa.quiet:not(.ylos):not(.alas):not(.tasan) { color: var(--ink-3); }

/* Kuittaus- ja tilaviestit olivat 16px eli leipatekstin kokoisia, vaikka
   ne seisovat 37px:n nappien vieressa. role="status" osuu tasmalleen
   naihin viiteen (#bkMsg, #myMsg, #autosendNote, #settingsSaved,
   #notifySaved) eika yhteenkaan eyebrow'hun. */
.quiet[role="status"] { font-size: 13px; line-height: 1.5; }

/* ---- DISABLED-TILA PUUTTUI KOKONAAN ----
   MITATTU 11.8.2026: nappi disabled-tilassa oli pikselilleen sama kuin
   ilman sita — opacity 1, cursor pointer, sama gradientti, sama reuna.
   Renderoija asettaa disabledin 25 kohdassa (tallennus, lahetys,
   hyvaksynta, peruutus, vienti) juuri sen sekunnin ajaksi jolloin
   pyynto on matkalla, joten omistaja painaa uudelleen koska mikaan ei
   kertonut etta paino meni perille. Ainoa komponentti jolla tila oli,
   oli .chat-lista-poista.

   Vaimennus + harmaannutus, ei uutta varia: gradientti sailyy mutta
   vetaytyy taustalle. */
.btn:disabled, .btn[disabled] {
  opacity: 0.42; cursor: not-allowed; box-shadow: none; filter: grayscale(0.35);
}
.btn:disabled:hover, .btn[disabled]:hover {
  transform: none; box-shadow: none; filter: grayscale(0.35);
}
.btn-primary:disabled:hover, .btn-primary[disabled]:hover { background: var(--act); }
.btn-ghost:disabled:hover,
.btn:not(.btn-primary):not(.btn-danger):disabled:hover,
.btn:not(.btn-primary):not(.btn-danger)[disabled]:hover {
  background: var(--d-surface); border-color: var(--d-hair-2);
}
.chat-send:disabled:hover { transform: none; }
.suggest-chip:disabled, .chat-tbtn:disabled, .j-more:disabled {
  opacity: 0.42; cursor: not-allowed;
}
.suggest-chip:disabled:hover, .chat-tbtn:disabled:hover, .j-more:disabled:hover {
  transform: none; color: inherit; border-color: var(--d-hair-2);
}
.input:disabled, textarea:disabled, .memory-edit:disabled {
  opacity: 0.5; cursor: not-allowed;
}

/* ---- KYTKIMET OLIVAT NAPPAIMISTON ULOTTUMATTOMISSA ----
   MITATTU 11.8.2026: .switch input oli display:none, eli 0x0 pikselia
   ja element.focus() ei ottanut. Tab-jarjestys hyppasi sivupalkista
   suoraan sisaltoon. Nappaimistolla ei siis paassyt YHTEENKAAN naista:
     #pauseSwitch    — Markin hatapysaytys ylapalkissa
     #setBooking, #setBookingAuto, #setDelivery, #setAutoSend
                     — kaikki neljä valtuusrajaa
   Se on WCAG 2.1.1 (taso A) eika vain kauneusasia.

   opacity:0 sailyttaa fokusoitavuuden, display:none ei. Ruutu jaa
   tasmalleen samannakoiseksi: kytkin on yha .track-elementti, ja
   pointer-events:none pitaa klikkauksen labelilla kuten ennenkin. */
.switch { position: relative; }
.switch input {
  display: block; position: absolute; inset: 0;
  width: 100%; height: 100%; margin: 0; padding: 0;
  opacity: 0; pointer-events: none;
}
.switch input:focus-visible { outline: none; }
.switch input:focus-visible + .track {
  outline: 2px solid var(--act-line); outline-offset: 3px;
}
/* #setBookingAuto asetetaan disabled-tilaan kun ylempi kytkin on pois.
   Ilman tata kytkin nayttaa toimivalta mutta ei liiku. */
.switch input:disabled + .track { opacity: 0.4; }
.switch:has(input:disabled) { cursor: not-allowed; }

/* ---- NOLLA OLI LIIAN HIMMEA LUETTAVAKSI ----
   MITATTU 11.8.2026: .stat .num.muted = --ink-3 opacity 0.5, eli
   tehollinen rgb(75,70,63) paneelin pintaa vasten = 2.01:1.
   Vaatimus isolle tekstille on 3:1. Renderoija asettaa .mutedin
   nimenomaan silloin kun luku on 0 ("Odottaa paatostasi 0"), eli
   himmein kohta sivulla oli oikea tieto eika paikanvaraaja.
   Opacity pois, vari ennallaan: hiljaisuus sailyy (--ink-3 vastaan
   --ink on yha yli kolminkertainen ero) mutta luku on luettava. */
.stat .num.muted { opacity: 1; }

/* ---- MUISTIPANEELI ROMAHTI 653 PIKSELIA ----
   MITATTU 11.8.2026: <pre id="memoryView"> kasvaa sisallon mukaan
   (973px tallä tilillä), <textarea id="memoryEdit"> on rows="12" eli
   noin 300px. "Muokkaa muistia" vaihtaa nama keskenaan, joten paneeli
   kutistui 1190px -> 537px ja juuri painettu nappi lensi 653px ylos
   ruudulla. Sama korkeus molemmille: vaihto tapahtuu paikallaan. */
.memory-view { max-height: 420px; overflow-y: auto; }
.memory-edit { min-height: 420px; }

/* ---- ASETUSRIVI KAPEALLA RUUDULLA ----
   MITATTU 390px:lla 11.8.2026: .set-row on flex jossa .ctrl ei jousta
   (flex-shrink:0), joten teksti puristui sen alta pois:
     "Antaa palautuksen kysymatta"  otsikkosarake 86px  -> rivi 300px korkea
     "Antaa alennuksen"             otsikkosarake 81px  -> rivi 234px korkea
   ja kaksi elementtia vuoti ruudun yli (#notifyEmail oikea reuna 409px,
   "Ota yhteytta lopettamisesta" 420px), eli sivu vieri sivusuunnassa
   30px. Roope katsoo taman puhelimella.

   Rivi saa nyt rivittya. .info pitaa 200px:n pohjan, joten kapea ohjain
   (kytkin 44px, merkki 87px) pysyy oikeassa reunassa kuten tyopoydalla
   ja leveä ohjain (kentta, pillinappi, pitka nappi) putoaa omalle
   riville taydelle leveydelle. Ei uutta asettelua, sama rivi joka
   taittuu. */
@media (max-width: 640px) {
  .set-row { flex-wrap: wrap; gap: 12px 16px; }
  .set-row .info { flex: 1 1 200px; min-width: 0; }
  .set-row .ctrl { flex-shrink: 1; min-width: 0; max-width: 100%; }
  .set-row .ctrl .btn { max-width: 100%; }
  .pill-input { max-width: 100%; }
}
/* Inline-tyylit poistettiin dashboard.html:sta: inline min-width voittaa
   jokaisen media-kyselyn, ja juuri se piti kentat ruudun levyisina.
   min() antaa saman 220/240px:n tyopoydalla ja paastaa kutistumaan
   kapealla. */
.set-row .ctrl .input { min-width: min(220px, 100%); }
.autosend-lisays {
  display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
}
/* MITATTU: .input on width:100%, joten se sai flex-perustaksi koko rivin
   ja "Lisaa listalle" tippui omalle rivilleen 55px alemmas jo 1440px:lla.
   Kentta ja nappi kuuluvat samalle riville. */
.autosend-lisays .input { flex: 1 1 240px; width: auto; min-width: min(240px, 100%); }
.autosend-lisays .btn { flex: none; }

/* ---- Muistilaatikko on ikkuna, ei katkennut teksti ----
   Yllä oleva max-height tekee laatikosta vieritettavan, ja tyhja leikkaus
   reunassa nayttaa rikkinaiselta. Sisavarjo kertoo etta teksti jatkuu
   reunan alla. Sama affordanssi kuin kapean sivupalkin haivytyksessa:
   katkaistu nayttaa lopulta, haipyva nayttaa jatkuvalta. */
.memory-view {
  box-shadow: inset 0 -20px 20px -20px rgba(0,0,0,0.85),
              inset 0  20px 20px -20px rgba(0,0,0,0.85);
}

/* ---- Lahetysnappi kertoo milloin silla on jotain lahetettavaa ----
   MITATTU 11.8.2026: #chatSend oli tyhjalla kentalla tasan yhta kirkas
   spektrinappi kuin viesti valmiina — taysi kutsu painaa nappia joka ei
   tee mitaan (send(): `var t = (input.value||'').trim(); if (!t) return;`).
   :placeholder-shown on tosi tasan silloin kun kentta on tyhja, ja
   textarea + nappi ovat sisaruksia samassa lomakkeessa, joten tama ei
   vaadi riviakaan JS:aa eika muuta toimintaa: nappi vain lakkaa
   lupaamasta. Ensimmaisella merkilla se syttyy. */
.chat-input textarea:placeholder-shown ~ .chat-send {
  opacity: 0.4; filter: grayscale(0.4);
}
.chat-input textarea:placeholder-shown ~ .chat-send:hover { transform: none; }
.chat-send { transition: opacity .2s ease, filter .2s ease, transform .2s cubic-bezier(.2,.8,.2,1); }

/* ---------------------------------------------------------------
   Varauskirja-linkin paikanvaraus.

   Linkki paljastetaan vasta kun palvelimelta tiedetaan onko yrityksella
   varattavia kohteita. Ennen tata se oli .hidden, jolloin sen
   ilmestyminen 625 ms kohdalla tyonsi neljä alempaa valikkokohtaa 51 px
   alas. Kayttaja joka klikkaa juuri silloin osuu vaaraan riviin, ja se on
   tasan se tuntuma jota "demomainen" tarkoittaa.

   .vk-varaus varaa paikan mutta ei nay eika ota klikkausta. Kun vastaus
   saapuu, luokka poistetaan: linkki joko jaa nakyviin tai saa .hidden.
   Yritykselle jolla on radat (Roope) valikko ei liiku lainkaan.
   --------------------------------------------------------------- */
.navlink.vk-varaus {
  visibility: hidden;
  pointer-events: none;
}

/* ---------------------------------------------------------------
   EHTOPORTTI (17.8.2026)

   Sama visuaalinen kieli kuin keskustelupaneelissa (.chat-lista),
   mutta z-index sen ylapuolella: ehtojen hyvaksynta voittaa kaikki
   muut kerrokset. Peite on tahallaan tummempi kuin chat-listan —
   taustalla oleva paneeli EI ole kaytettavissa ennen hyvaksyntaa,
   ja peitteen on kerrottava se.
   --------------------------------------------------------------- */
.ehdot-peite {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(9,7,5,0.86);
}
.ehdot-peite[hidden] { display: none; }

.ehdot-portti {
  position: fixed; z-index: 71;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: min(600px, calc(100vw - 32px));
  max-height: min(86vh, 720px); overflow-y: auto;
  padding: 26px 28px 22px;
  background: var(--d-modal);
  border: 1px solid var(--d-hair-2);
  border-radius: var(--r-panel);
  box-shadow: 0 40px 90px rgba(0,0,0,0.70);
}
.ehdot-portti[hidden] { display: none; }
.ehdot-portti h2 { margin: 0 0 12px; font-size: 21px; }
.ehdot-intro { margin: 0 0 14px; line-height: 1.6; }
.ehdot-linkit { margin: 0 0 16px; line-height: 1.9; }

/* Vakuutusteksti on se osa joka kirjataan kantaan — se erotetaan
   muusta tekstista, jotta on yksiselitteista mika hyvaksyttiin. */
.ehdot-vakuutus {
  margin: 0 0 16px; padding: 14px 16px;
  border: 1px solid var(--d-hair);
  border-radius: var(--r-field, 10px);
  font-size: 14.5px; line-height: 1.65;
}

.ehdot-napit { display: flex; gap: 10px; flex-wrap: wrap; margin: 0 0 14px; }
.ehdot-kirjaus { margin: 0; font-size: 13px; line-height: 1.6; opacity: 0.75; }

/* ============================================================
   MUSTA YLÄPALKKI POIS KAPEALTA NÄYTÖLTÄ
   Simo 29.8.2026, puhelimella:

     "poista tämä koko 'Mark töissä' musta palkki. Se blokkaa paljon
      näkymästä ja nyt kun siinä ei edes lue Mark töissä se on lähinnä
      vain hämmentävä. Voit laittaa Mark töissä kohdan ja napin joko
      etusivulle eli Tilanne nyt -sivulle tai asetuksiin. Mutta musta
      palkki käytäntö pois."

   Kutistin 27.8. saman palkin sisältöä kolmessa erässä: tilamerkin
   pisteeksi, alaotsikon pois chatista, lopulta koko palkin pois
   chatista. Jokainen erä oli mitattu ja jokainen paransi lukuja, mutta
   yksikään ei kysynyt tarvitaanko palkkia lainkaan. Lopputulos oli
   pahin mahdollinen: jäljellä oli musta vyöhyke jossa ei enää lukenut
   mitään, eli se vei tilaa antamatta tietoa.

   OPETUS: kun sama pinta joutuu kutistuslistalle kolmesti, kysymys ei
   ole enää mitoituksesta vaan siitä pitääkö sen olla olemassa.

   Näkymän nimi ei katoa: se lukee navipalkissa korostettuna. Työpöydällä
   palkki jää, siellä se ei ole keneltäkään pois.
   ============================================================ */
@media (max-width: 920px) {
  .topbar { display: none; }
}

/* Tilamerkki ja keskeytyskytkin asuvat nyt Tilanne nyt -näkymässä
   (dashboard.html, #view-overview). Ne olivat yläpalkin flex-lapsia,
   joten omaa muotoilua ei ollut olemassa: `.mark-status` on pelkkä
   `display: flex; gap: 18px`, mikä paljaana lohkona sivun ylälaidassa
   näyttäisi irralliselta rivilta. Sama laatikkokieli kuin muilla
   näkymän lohkoilla, ja kytkin oikeaan reunaan jotta se on peukalon
   ulottuvilla puhelimessa. */
#view-overview > .mark-status {
  justify-content: space-between;
  gap: 16px;
  margin: 0 0 22px;
  padding: 13px 18px;
  border: 1px solid var(--d-hair);
  border-radius: var(--r-field, 12px);
  /* LEVEYS SISALLON MUKAAN, EI SARAKKEEN  (1.9.2026)
     Mitattu kolmella leveydella, palkin tyhja osuus:
       390 px  palkki 342 px, sisalto 191 px  ->  33 % tyhjaa
       768 px  palkki 720 px, sisalto 191 px  ->  68 %
      1440 px  palkki 1008 px, sisalto 191 px ->  77 %
     Sisalto on 191 px riippumatta ruudun leveydesta: tilamerkki ja
     kytkin. Tyopoydalla kolme neljasosaa palkista oli siis tyhjaa ja
     44 pikselin kytkin kellui yksin oikeassa reunassa 779 pikselin
     paassa siita mita se ohjaa.
     Kytkimen sijainti oikeassa reunassa on tarkoituksellinen ja
     perusteltu ylla: peukalon ulottuvilla puhelimessa. Se perustelu
     patee 390 px:lla jossa vali on 113 px, mutta ei 1440 px:lla.
     max-width korjaa vain tyopoydan: mobiilissa palkki on jo 342 px
     eli kapeampi kuin raja, joten siella mikaan ei muutu eika uutta
     katkaisupistetta tarvita. Palkki lukee nyt tilamerkkina eika
     osiona, mika se onkin. */
  max-width: 360px;
}

/* CHATIN VIERITYS ON HYPPY, EI MATKA.
   app.css asettaa `html { scroll-behavior: smooth }`, ja se peritään myös
   ohjelmalliseen vieritykseen. MITATTU 29.8.2026: chatin avaaminen kelasi
   6518 px alas noin sekunnin ajan, eli omistaja näki ensin historian
   vanhimman viestin ja katsoi sitten animaatiota. Sama koski jokaista
   striimatun vastauksen palaa: auth-supabase.js vierittää alas kymmenessä
   eri kohdassa, ja jokainen niistä oli pehmeä.

   Tämä sääntö tekee kaikista niistä välittömiä yhdellä rivillä, sen sijaan
   että jokaiseen kutsukohtaan lisättäisiin `behavior: 'auto'`. Sivun oma
   pehmeä vieritys (näkymänvaihdon window.scrollTo) säilyy ennallaan. */
#chatBody { scroll-behavior: auto; }

/* --- 2. Yläpalkki ei leiju chatin päällä ---
   Alle 920 px:n `.chat-shell` on `height: auto; min-height: 70vh`
   (app.css), eli chat ei ole oma vieritysalueensa vaan vierii sivun
   mukana. Silloin sticky-yläpalkki on suoraan pois viestien tilasta
   koko keskustelun ajan.

   Muissa näkymissä sticky on oikein: siellä yläpalkki kertoo missä
   ollaan, ja sisältö on lyhyitä lohkoja. Chatissa sen tehtävän hoitaa
   jo .chat-head ("Mark · Paikalla, vastaa heti"), joka vierii mukana
   omalla paikallaan. Sama tieto kahdesti, ja se kalliimpi kopio vei
   pysyvästi ruudun ylälaidan.

   body[data-view] tulee dashboard.js:n showView'sta. */
@media (max-width: 920px) {
  /* (Chatin oma yläpalkkisääntö poistettu 29.8.2026: palkki on nyt
     piilotettu kaikista näkymistä kapealla näytöllä, ks. lohko
     "MUSTA YLÄPALKKI POIS KAPEALTA NÄYTÖLTÄ". Erillinen chat-sääntö oli
     vain sen jäänne, ettei koko palkkia uskallettu vielä poistaa.) */

  /* --- Chat mahtuu ruudulle kokonaan, ei mikään leiju ---
     MITATTU 27.8.2026, kuvakaappauksesta 390x844: pelkkä sticky-poisto
     ei riittänyt. `.chat-body` on `flex: 1; overflow-y: auto`, eli chat
     vierii itse, ja `.chat-shell` on mobiilissa vain `min-height: 70vh`.
     Sivu jäi siis 54 px:n mittaiseksi vieritykseksi: yläpalkki ei
     päässyt vierimään pois vaan sen viimeiset 25 px jäivät roikkumaan
     navipalkin alapuolelle leikattuna kaarena. Se näytti rikkinäiseltä
     eikä siistiltä, ja tila oli yhä poissa chatilta.

     Oikea muoto mobiilichatille ei ole vierivä sivu vaan ruudun
     mittainen näkymä: navipalkki ylös, otsikko sen alle, ja loput
     chatille niin että kirjoituskenttä on aina alalaidassa. Silloin
     sivulla ei ole mitään vieritettävää eikä mikään voi jäädä
     puoliksi näkyviin.

     Ei kovia pikselilukuja: `.app` on jo grid, joten `auto 1fr` antaa
     navipalkille sen oman korkeuden ja lopun mainille. 100dvh eikä
     100vh, koska mobiiliselaimen osoitepalkki syö vh:sta osan ja
     kirjoituskenttä jäisi sen alle. vh-rivi on fallback vanhoille. */
  /* grid-template-rows tulee alempaa yleissäännöstä ("Navipalkki venyi
     lyhyissä näkymissä"); tässä tarvitaan enää korkeuden lukitus. */
  body[data-view="chat"] .app {
    height: 100vh;
    height: 100dvh;
  }
  body[data-view="chat"] .main { display: flex; flex-direction: column; min-height: 0; }
  body[data-view="chat"] #view-chat.view {
    flex: 1; min-height: 0; display: flex; flex-direction: column;
  }
  body[data-view="chat"] .chat-shell { flex: 1; height: auto; min-height: 0; }
}

/* ============================================================
   YKSI NAVIGAATIO KAPEALLA NÄYTÖLLÄ, EI KAHTA
   Web Design -agentin löydös 27.8.2026 (VASTAUS_S4_Seppo.md, kohta 1),
   ja se näkyi myös Simon omassa kuvakaappauksessa tuotannosta.

   Puhelimessa oli yhtä aikaa kaksi navigaatiota: vaakasuunnassa vierivä
   valikkorivi ylhäällä JA hampurilaisnappi sen alla. Sama asia kahdesti,
   kahdella eri tavalla.

   MITATTU 390 px:llä:
     .side scrollWidth 1822 px / clientWidth 389 px = 4,7 ruutua
     valikkokohtia näkyvissä kokonaan: 3 / 12
     rivi vie pystysuunnassa 71 px joka näkymässä, pysyvästi

   Vaakarivi ei siis ollut navigaatio vaan kompromissi: se näytti neljäsosan
   kohdista ja vaati vaakavierityksen lopuista. Hampurilainen taas näyttää
   kaikki kaksitoista, yrityksen nimen ja uloskirjautumisen.

   ⚠ MIKSI EI PÄINVASTOIN. Agentti suositti hampurilaisen poistamista
   yksinkertaisempana. Mittasin sen ennen toteutusta: `.side-foot` on
   mobiilissa `display: none` ja tulee näkyviin vain `.mobile-open`illa,
   joten ilman hampurilaista `#logoutBtn` on 0 x 0 px. Se olisi poistanut
   uloskirjautumisen puhelimesta kokonaan. Juuri tuon takia hampurilainen
   kytkettiin päälle 11.8.2026 (ks. app.css rivi 304). Ehdotus oli
   toimintaregressio, ei viimeistely.

   ⚠ MIKSI EI TOISINPÄIN, ELI VAAKARIVI PIILOON JA HAMPURILAINEN AINOAKSI.
   Kokeilin sitä ensin ja mittasin sen: silloin jokainen näkymänvaihto vaatii
   kaksi napautusta yhden sijaan. Simo käyttää chattia puhelimella joka
   päivä, ja "Puhu Markille" on vaakarivin toinen kohta eli näkyvissä ilman
   vieritystä. Se olisi hidastanut hänen omaa käyttöään joka kerta säästääkseen
   71 px, ja nopeus on hänelle arvo. Vaakarivi on lisäksi tilamerkki: siitä
   näkee missä ollaan avaamatta mitään.

   Jäljelle jää siis vaakarivi, ja hampurilainen poistuu. Sen mukana lähtevät
   myös 58 px:n otsikkosisennys ja ruksiksi jäänyt ikoni.

   ⚠ MITÄ TÄMÄ EI SAA RIKKOA. Hampurilainen kytkettiin päälle 11.8.2026
   nimenomaan siksi, että `.side-foot` (Kirjaudu ulos) ja `.biz` (yrityksen
   nimi) ovat mobiilissa `display: none` ja tulivat näkyviin vain
   `.mobile-open`illa. Mitattu: ilman valikkoa `#logoutBtn` on 0 x 0 px.
   Pelkkä hampurilaisen poisto olisi siis vienyt uloskirjautumisen
   puhelimesta kokonaan. Siksi uloskirjautuminen tuodaan tässä samalla
   vaakariville, rivin viimeiseksi kohdaksi.
   ============================================================ */
@media (max-width: 920px) {
  .burger { display: none; }

  /* --- Navipalkki venyi lyhyissä näkymissä ---
     MITATTU 27.8.2026, 390 px. Navipalkin korkeus vaihteli näkymän mukaan
     71,2 px:stä 176,7 px:iin, eli pahimmillaan se söi 21 % ruudusta.
     Sisällöllä ei ollut mitään tekemistä asian kanssa: jokainen valikkokohta
     on 46 px korkea kaikissa näkymissä, ja ne olivat pystysuunnassa
     keskitettyinä 65 px:n tyhjän päälle ja alle.

     Syy on `.app`: se on grid jolla on `min-height: 100vh`, ja mobiilissa
     rivejä on kaksi (navipalkki, sisältö) molemmat `auto`. Kun näkymän
     sisältö on lyhyt eikä ruutu täyty, grid jakaa ylijäävän tilan RIVIEN
     KESKEN, ja navipalkki sai osansa. Pitkässä näkymässä (Tilanne nyt,
     1619 px sisältöä) ylijäämää ei ole, joten palkki oli 71,2 px ja vika
     oli näkymätön. Siksi se on elänyt näin pitkään.

     `auto 1fr` antaa navipalkille sen oman korkeuden ja koko ylijäämän
     sisällölle, jonne se kuuluu. */
  .app { grid-template-rows: auto 1fr; }

  /* Uloskirjautuminen vaakarivin viimeiseksi. app.css antaa .side-footille
     pystyvalikon muodot (margin-top: auto työntää sen pohjaan, yläviiva
     erottaa sen listasta) — vaakarivillä ne kaikki ovat vääriä. */
  .side .side-foot {
    display: block; margin-top: 0; padding-top: 0;
    border-top: none; flex-shrink: 0;
  }
}

/* Otsikko samaan linjaan sisällön kanssa. Hampurilainen työnsi h1:n
   82,0 px:iin kun sisältö alkaa 24,0 px:stä; nappi on nyt poissa, joten
   jäljelle jää vain yläpalkin oma 20 px:n reunus. 24 px on sama arvo kuin
   .view'llä samalla leveydellä (dashboard.css rivi 724 vs app.css .view). */

/* ============================================================
   MITÄ OLEN HUOMANNUT (30.8.2026)
   Simo: "Dashboardiin olisi hyvä luoda kohta jossa olisi kaikki Markin
   havainnot eikä vain sähköpostissa."

   Kolme sitovaa vaatimusta ohjaavat tätä tyyliä:
   1. Uusi erottuu luetusta. Se on vasemman reunan väriviiva ja
      taustan sävy, EI koko rivin korostus: kymmenen korostettua riviä
      ei korosta mitään.
   2. Havainto kertoo mistä se tuli. `laji` on oma merkkinsä, ja kolme
      lajia saa kolme väriä koska ne ovat omistajalle eri asioita:
      ehdotus on tarjous, "odottaa sinua" vaatii toimia, huomio ei.
   3. Havainto ei saa hukkua. Rivit ovat väljiä ja otsikko on
      luettavaa kokoa; tämä on lukulista, ei taulukko.
   ============================================================ */
.hav-lista { display: flex; flex-direction: column; gap: 12px; margin-top: 18px; }

.hav {
  border: 1px solid var(--d-hair);
  border-left: 3px solid transparent;
  border-radius: var(--r-field, 12px);
  padding: 15px 18px;
}
/* Luettu vaimenee muttei katoa: se on yhä luettavissa, ja poistaminen
   veisi omistajalta mahdollisuuden palata siihen. */
.hav:not(.uusi) { opacity: 0.72; }
.hav.uusi {
  border-left-color: var(--amb, #f6a961);
  background: rgba(246, 169, 97, 0.05);
}

.hav-yla { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }

.hav-laji {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.12em; text-transform: uppercase;
  padding: 4px 10px; border-radius: var(--r-pill);
  border: 1px solid var(--d-hair-2); color: var(--ink-2);
}
/* Kolme lajia, kolme merkitystä. Värit ovat samat kuin muualla
   tuotteessa: turkoosi on tarjous ja mahdollisuus, oranssi vaatii
   ihmistä, harmaa on pelkkä huomio. */
.hav-laji.laji-valmennus  { color: var(--data); border-color: rgba(52,194,174,0.32); background: rgba(52,194,174,0.09); }
.hav-laji.laji-eskalaatio { color: var(--alert); border-color: rgba(246,169,97,0.32); background: rgba(246,169,97,0.10); }

/* RATKENNUT: vihreä eikä harmaa. Tämä on ainoa kohta koko näkymässä
   jossa jokin on mennyt hyvin ilman että omistaja teki mitään, ja se
   kannattaa näyttää siltä. Havainto sulkeutuu itsestään kun tieto
   ilmestyy tietopohjaan. */
.hav-ratkennut {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.12em; text-transform: uppercase;
  padding: 4px 10px; border-radius: var(--r-pill);
  color: var(--data); border: 1px solid rgba(52,194,174,0.30);
  background: rgba(52,194,174,0.08);
}
.hav.ratkennut { opacity: 0.6; }
.hav.ratkennut .hav-otsikko { text-decoration: line-through; text-decoration-thickness: 1px; }

.hav-aika {
  margin-left: auto; font-family: var(--body); font-weight: 300;
  font-size: 13px; color: var(--ink-3); white-space: nowrap;
}
.hav-otsikko { margin: 0 0 6px; font-size: 14.5px; line-height: 1.45; color: var(--ink); }
/* RIVINMITTA (mitattu 1.9.2026, 1440 px). Ilman rajaa .hav-kuvaus venyi
   100 merkkiin ja .note 117 merkkiin viidessa nakymassa. Luettavuuden
   vakiintunut alue on 45-75 merkkia: sita leveammalla silma ei loyda
   seuraavan rivin alkua ilman etsimista ja lukija palaa samalle riville.
   Se ei nay virheena, se tuntuu raskaalta. 62ch on sama arvo jota kahdeksan
   muuta saantoa jo kaytti, joten tama liittyy olemassa olevaan asteikkoon
   eika luo uutta. */
.hav-kuvaus  { margin: 0; font-size: 14.5px; line-height: 1.6; color: var(--ink-2); max-width: 62ch; }
.note        { max-width: 62ch; }
/* Ratkaisuteksti erottuu kuvauksesta vasemmalla viivalla: kuvaus on
   Markin selitys siita miksi han ei pystynyt, tama on se mita asialle
   sitten tehtiin. Ne ovat eri aanet ja eri hetket, ja peräkkäisinä
   kappaleina ne sekoittuisivat samaksi tekstiksi. */
.hav-ratkaisu {
  margin: 8px 0 0; padding: 8px 0 8px 12px;
  border-left: 3px solid var(--ok, #2e7d32);
  font-size: 14.5px; line-height: 1.6; color: var(--ink-1, var(--ink-2));
}
/* .hav-nahty ja .hav-hoida eivat enaa tarvitse omaa yla-marginaalia:
   nappirivi on 2.9.2026 alkaen .draft-actions-kontti (jaettu app.css:sta,
   sama gap+margin-top kuin tyojonon napeilla), joka omistaa valin. */

/* Kapealla näytöllä aika omalle rivilleen: pitkä laji + päivämäärä ei
   mahdu 390 px:iin, ja `margin-left: auto` työntäisi ajan ulos
   reunasta. Sama vikaluokka kuin yläpalkin tilamerkissä 27.8. */
@media (max-width: 560px) {
  .hav-yla { flex-wrap: wrap; gap: 8px; }
  .hav-aika { margin-left: 0; width: 100%; }
}

/* ============================================================
   VIIMEISTELY 31.8.2026
   Web Design -agentin mittaamat loydokset 5, 6, 11 ja 12
   (raportti web-design-agentti/VASTAUS_S4_Seppo.md, 27.8.2026).
   Jokainen arvo alla on raportin oma, selaimessa mitattu arvo.
   Loydokset 7 ja 13 on viety paikalleen ylempana tassa tiedostossa
   (pointer: coarse -lohko ja .view.active-animaatio).

   HUOM seuraavalle lukijalle: nama saannot yliajavat tarkoituksella
   aiempia arvoja samassa tiedostossa (.panel rivi ~147 sanoo yha 30px).
   Se on raportin valitsema tapa, koska osa kohteista (.draft-card,
   .k-item) asuu app.css:ssa jota myos login.html ja operator.html
   lataavat, eika muutoksen kuulu vuotaa niille sivuille.
   ============================================================ */

/* ---- 5. Pyoristys: kolme roolia, ei viitta ----
   Mitattu: 10 (.draft-card, .k-item.pending), 12 (.my-kpi, .memory-view,
   .sm-col), 16 (.msg .bubble), 18 (.panel, .stat, .statusline),
   20 (.council-form). Tokenit --r-panel: 18px ja --r-field: 12px olivat
   jo olemassa, kolme paikkaa vain ei kayttanyt niita.
   Chat-kupla (16px + hantakulma) on neljas, tietoinen poikkeus. */
.draft-card,
.k-item,
.k-item.pending,     /* .k-item.pending voittaa yhden luokan valitsimen */
.council-form { border-radius: var(--r-field); }

/* ---- 6. Sisareunus: kolme tasoa craft-asteikolta ----
   Mitattu kymmenen arvoa (6, 12, 14, 16, 18, 22, 24, 26, 28, 30), joista
   seitseman on asteikon 4/8/12/16/24/32 ulkopuolella. Kolme paallimmaista
   pintaa erosivat toisistaan alle 8 %, eli ne eivat erottaneet mitaan.
   Paneeli 24, kortti 16, tiivis 12. */
.panel      { padding: 24px; }   /* oli 30px */
.stat       { padding: 24px; }   /* oli 26px 28px 24px */
.statusline { padding: 24px; }   /* oli 30px 32px */
@media (max-width: 920px) { .statusline { padding: 24px; } }  /* oli 24px 22px */

.draft-card, .my-kpi, .memory-view { padding: 16px; }
.sm-col, .k-item.pending           { padding: 12px; }

/* ---- 11. Tyhjan tilan leipateksti vasempaan ----
   Mitattu 390px:lla: #view-drafts .empty p on 224px leveä ja 27 merkkia
   rivilla seitseman rivin ajan, #councilEmpty p 36 merkkia. Craft-saanto
   on 45-75 merkkia. Otsikko ja ikoni jaavat keskelle, selitys tasataan
   vasempaan. .empty on flex-column + align-items: center (app.css 341),
   joten leipateksti tarvitsee align-self:in. */
.empty p,
.council-empty p {
  text-align: left;
  align-self: stretch;
  max-width: 62ch;
}
@media (max-width: 560px) {
  .empty { padding: 48px 4px; }   /* oli 64px 28px: 56px reunusta tekstille */
  .council-empty { text-align: center; }
  .council-empty p { text-align: left; }
}

/* ---- 12. Otsikkotasoja kaksi, ei nelja ----
   Mitattu h3 nelja kertaa: 28px (neuvosto), 26px (chat), 22px (drafts),
   21px/500 (paneeliotsikot). Paneeliotsikko on jo 21px/500 rivilla ~150,
   eli se ei tarvitse muutosta. Tyhjan tilan otsikko on oma roolinsa ja
   saa olla suurempi, mutta yksi koko riittaa kolmen sijaan. */
.empty h3, .chat-empty h3, .council-empty h3, .j-empty h3 {
  font-family: var(--display); font-weight: 300;
  font-size: clamp(24px, 2.6vw, 30px); letter-spacing: -0.02em;
}

/* Myyntiraportin valiotsikko kaytti Interia 12px:lla muiden otsikoiden
   joukossa. Se ei ole otsikko vaan silmakulmateksti, joten sen kuuluu
   puhua .eyebrow'n kielta eika esiintya h4:na. */
.my-otsikko {
  font-family: var(--body); font-weight: 500; font-size: 11px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-3);
}
/* ============================================================
   RIVITOIMINTO EI OLE SIVUN PÄÄTOIMINTO  (1.9.2026)

   Mitattu yhdestätoista näkymästä, kuinka monta gradienttinappia
   (.btn-primary) kussakin on näkyvissä:

     overview 0 · chat 1 · drafts 0 · sivumuutokset 0 · havainnot 0
     tyojono 1 · bookings 0 · journal 0 · council 1 · settings 2
     knowledge 4

   Yhdeksässä yhdestätoista on nolla tai yksi, ja settingsin kaksi ovat
   kahden eri lomakkeen tallennus. Knowledge on ainoa poikkeus: siellä
   sama gradientti toistuu neljästi listan riveillä.

   --grad on Markin identiteetti, sama paletti kuin orbissa, ja se
   merkitsee "tämän näkymän päätoiminto". Kun se toistuu neljästi
   allekkain, se lakkaa merkitsemästä sitä ja alkaa lukea koristeena.
   Se on sama vika kuin kaksi kilpailevaa korostusväriä, vain toistona.

   Hierarkia rivillä säilyy täysin: Vahvista on yhä täytetty ja Hylkää
   ääriviiva, joten vastakkaiset toiminnot eivät sekoitu. Vain painoarvo
   suhteessa sivun päätoimintoon laskee.
   ============================================================ */
.k-act .btn-primary {
  background: rgba(245, 240, 232, 0.24);
  color: var(--ink);
  border-color: rgba(245, 240, 232, 0.36);
  box-shadow: none;
}
.k-act .btn-primary:hover {
  background: rgba(245, 240, 232, 0.20);
  border-color: rgba(245, 240, 232, 0.34);
  filter: none;
  box-shadow: none;
}

/* ============================================================
   FOKUSILMAISIN LOMAKEKENTILLE  (1.9.2026)

   Mitattu Tab-navigaatiolla viidestä näkymästä: 180 elementillä on
   näkyvä fokusilmaisin, 15:llä ei. Kaikki 15 ovat lomakekenttiä.

   Juurisyy on spesifisyys, ei puuttuva sääntö. Yleinen sääntö
   (dashboard.css ~1789) asettaa vain outline-COLORin, jotta kunkin
   säännön oma leveys ja offset säilyvät. Se ei voi palauttaa outlinea
   jonka toinen sääntö on poistanut:

     app.css       .pill-input input:focus     { outline: none; }
     dashboard.css .council-form textarea:focus { outline: none; box-shadow: none; }

   Molemmat ovat (0,2,1) ja käyttävät :focus eivätkä :focus-visible,
   joten ne voittavat yleisen (0,1,1) myös näppäimistöllä. Kenttä jää
   ilman ilmaisinta, eli näppäimistökäyttäjä ei näe mihin hän kirjoittaa.
   WCAG 2.4.7 vaatii näkyvän fokuksen.

   Ilmaisin annetaan SÄILIÖLLE eikä kentälle. Kumpikin kenttä on
   visuaalisesti osa säiliötään (pilleri, lomakekehys), joten outline
   kentän ympärillä olisi laatikko laatikossa. :focus-within antaa saman
   tiedon säiliön omalla muodolla. Väri on sama --act-line jota kaikki
   muut fokusilmaisimet käyttävät.
   ============================================================ */
.pill-input:focus-within {
  border-color: var(--act-line);
  box-shadow: 0 0 0 3px rgba(185, 139, 230, 0.22);
}
.council-form:focus-within {
  border-color: var(--act-line);
  box-shadow: 0 0 0 3px rgba(185, 139, 230, 0.18);
}
