{"id":281,"date":"2026-07-10T19:50:34","date_gmt":"2026-07-10T16:50:34","guid":{"rendered":"https:\/\/coachingcircle.fi\/info\/?page_id=281"},"modified":"2026-07-12T22:28:26","modified_gmt":"2026-07-12T19:28:26","slug":"sosiaalinen-paaoma","status":"publish","type":"page","link":"https:\/\/coachingcircle.fi\/info\/tyokalut\/sosiaalinen-paaoma\/","title":{"rendered":"Sosiaalisen p\u00e4\u00e4oman kartoitus"},"content":{"rendered":"\n<div id=\"main-content\">\n<div class=\"title-row\">\n<span class=\"badge\"><a href=\"https:\/\/coachingcircle.fi\/info\/tyokalut\/\">Ilmainen ty\u00f6kalu<\/a><\/span> \n<span class=\"author\">Coaching Circle<\/span> \n<\/div>\n\n<h1 class=\"tool-name\">Sosiaalisen p\u00e4\u00e4oman kartoitus\u00adty\u00f6kalu<\/h1>\n<figure class=\"wp-block-image aligncenter size-full is-resized\" style=\"display: block; margin:0 auto;\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"896\" src=\"https:\/\/coachingcircle.fi\/info\/wp-content\/uploads\/tyokalu-sosiaalisen-paaoman-kartoitus.png\" alt=\"Maksuton ty\u00f6kalu - Sosiaalisen p\u00e4\u00e4oman kartoitus\" class=\"wp-image-472\" style=\"width: 100%; max-width: 420px;\" srcset=\"https:\/\/coachingcircle.fi\/info\/wp-content\/uploads\/tyokalu-sosiaalisen-paaoman-kartoitus.png 1200w, https:\/\/coachingcircle.fi\/info\/wp-content\/uploads\/tyokalu-sosiaalisen-paaoman-kartoitus-640x478.png 640w, https:\/\/coachingcircle.fi\/info\/wp-content\/uploads\/tyokalu-sosiaalisen-paaoman-kartoitus-320x239.png 320w, https:\/\/coachingcircle.fi\/info\/wp-content\/uploads\/tyokalu-sosiaalisen-paaoman-kartoitus-768x573.png 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/figure>\n    <section class=\"tool-shell\">\n      <span class=\"progress-label\">Edistyminen:<\/span>\n      <div class=\"progress-region\" aria-label=\"Edistyminen\">\n        <div class=\"progress-meta\">\n          <span id=\"progress-percent\">0 %<\/span>\n        <\/div>\n        <div class=\"progress-track\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"0\" aria-labelledby=\"progress-label\">\n          <div class=\"progress-bar\" id=\"progress-bar\"><\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"status-banner\" id=\"status-banner\" role=\"status\" aria-live=\"polite\" hidden=\"\"><\/div>\n      <div class=\"app-view\" id=\"app\"><\/div>\n    <\/section>\n  <\/div>\n\n  <div class=\"site-footer\">\n    <p>Mukautettu Dmitri Williamsin Internet Social Capital Scales -mittarista (2006) ja <em>Greater Good  Centerin &#8220;Social Capital&#8221; -kyselyst\u00e4<\/em>. T\u00e4m\u00e4 on suuntaa-antava itsearviointi, ei alkuper\u00e4isten tahojen virallinen suomenkielinen validointi eik\u00e4 terveydenhuollon arvio.<\/p>\n  <\/div>\n\n  <noscript>\n    &amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;p style=&#8221;max-width:760px;margin:32px auto;padding:20px;background:#fff;border-radius:14px;&#8221;&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;T\u00e4m\u00e4 ty\u00f6kalu tarvitsee JavaScriptin toimiakseen.&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;\/p&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;\n  <\/noscript>\n\n\n\n<script data-wp-block-html=\"js\">\n\/*\n      COACHING CIRCLE - SOSIAALISEN P\u00c4\u00c4OMAN KARTOITUS\n      ------------------------------------------------\n      Kaikki CSS ja JavaScript ovat t\u00e4ss\u00e4 tiedostossa. OpenAI-avainta ei saa koskaan\n      lis\u00e4t\u00e4 t\u00e4h\u00e4n sivuun. Selain l\u00e4hett\u00e4\u00e4 vastaukset omaan backendiin, joka:\n\n      1) validoi kysymystunnukset ja arvot,\n      2) laskee pisteet uudelleen palvelimella,\n      3) kutsuu OpenAI Responses API:a palvelinpuolelta,\n      4) palauttaa rakenteisen JSON-vastauksen sivulle.\n\n      Voit ylikirjoittaa asetukset ENNEN t\u00e4t\u00e4 skripti\u00e4:\n\n      window.COACHING_CIRCLE_SOCIAL_CAPITAL_CONFIG = {\n        apiBase: \"\/api\/social-capital\",\n        csrfEndpoint: \"\/api\/csrf\",\n        privacyUrl: \"https:\/\/coachingcircle.fi\/tietosuojakaytanto\/\",\n        coachDirectoryUrl: \"\/coaches\",\n        requestTimeoutMs: 12000,\n        demoFallback: false\n      };\n\n      Odotetut endpointit:\n      POST \/api\/social-capital\/assessments\n      PUT  \/api\/social-capital\/assessments\/{assessmentId}\/answers\/{questionId}\n      POST \/api\/social-capital\/assessments\/{assessmentId}\/analyze\n\n      Tuotannossa demoFallback kannattaa asettaa false-arvoon.\n    *\/\n\n    (() => {\n      \"use strict\";\n\n      const DEFAULT_CONFIG = Object.freeze({\n        apiBase: \"\/api\/social-capital\",\n        csrfEndpoint: \"\/api\/csrf\",\n        privacyUrl: \"https:\/\/coachingcircle.fi\/tietosuojakaytanto\/\",\n        coachDirectoryUrl: \"\/coaches\",\n        requestTimeoutMs: 12000,\n        demoFallback: true\n      });\n\n      const CONFIG = Object.freeze({\n        ...DEFAULT_CONFIG,\n        ...(window.COACHING_CIRCLE_SOCIAL_CAPITAL_CONFIG || {})\n      });\n\n      const INSTRUMENT_VERSION = \"cc-social-capital-fi-1.0\";\n      const STORAGE_KEY = `coaching-circle-social-capital:${INSTRUMENT_VERSION}`;\n\n      const LIKERT_OPTIONS = Object.freeze([\n        { value: 1, label: \"T\u00e4ysin eri mielt\u00e4\" },\n        { value: 2, label: \"Jokseenkin eri mielt\u00e4\" },\n        { value: 3, label: \"Ei samaa eik\u00e4 eri mielt\u00e4\" },\n        { value: 4, label: \"Jokseenkin samaa mielt\u00e4\" },\n        { value: 5, label: \"T\u00e4ysin samaa mielt\u00e4\" }\n      ]);\n\n      const AGE_OPTIONS = Object.freeze([\n        { value: \"under_18\", label: \"Alle 18\" },\n        { value: \"18_29\", label: \"18\u201329\" },\n        { value: \"30_39\", label: \"30\u201339\" },\n        { value: \"40_49\", label: \"40\u201349\" },\n        { value: \"50_59\", label: \"50\u201359\" },\n        { value: \"60_69\", label: \"60\u201369\" },\n        { value: \"70_plus\", label: \"70 tai enemm\u00e4n\" },\n        { value: \"prefer_not_to_say\", label: \"En halua vastata\" }\n      ]);\n\n      const QUESTIONS = Object.freeze([\n        {\n          id: \"q01\",\n          number: 1,\n          kind: \"likert\",\n          category: \"offlineBonding\",\n          reverse: false,\n          text: \"Kun tunnen oloni yksin\u00e4iseksi, minulla on arjessa useita ihmisi\u00e4, joille voin puhua.\"\n        },\n        {\n          id: \"q02\",\n          number: 2,\n          kind: \"likert\",\n          category: \"onlineBridging\",\n          reverse: false,\n          text: \"Verkossa kohtaamani ihmiset her\u00e4tt\u00e4v\u00e4t minussa kiinnostusta siihen, mit\u00e4 muut ihmiset ajattelevat.\"\n        },\n        {\n          id: \"q03\",\n          number: 3,\n          kind: \"likert\",\n          category: \"onlineBridging\",\n          reverse: false,\n          text: \"Olen valmis k\u00e4ytt\u00e4m\u00e4\u00e4n aikaa yhteisten verkkoyhteis\u00f6jen toiminnan tukemiseen.\"\n        },\n        {\n          id: \"q04\",\n          number: 4,\n          kind: \"likert\",\n          category: \"offlineBonding\",\n          reverse: false,\n          text: \"Arjessa tuntemani ihmiset auttaisivat minua toimimaan ep\u00e4oikeudenmukaisuutta vastaan.\"\n        },\n        {\n          id: \"q05\",\n          number: 5,\n          kind: \"likert\",\n          category: \"onlineBonding\",\n          reverse: true,\n          text: \"En tunne verkossa tapaamiani ihmisi\u00e4 niin hyvin, ett\u00e4 voisin pyyt\u00e4\u00e4 heit\u00e4 mukaan johonkin minulle t\u00e4rke\u00e4\u00e4n asiaan.\"\n        },\n        {\n          id: \"q06\",\n          number: 6,\n          kind: \"likert\",\n          category: \"onlineBridging\",\n          reverse: false,\n          text: \"Verkossa toimiminen tuo el\u00e4m\u00e4\u00e4ni uusia ihmisi\u00e4, joiden kanssa voin keskustella.\"\n        },\n        {\n          id: \"q07\",\n          number: 7,\n          kind: \"likert\",\n          category: \"onlineBonding\",\n          reverse: false,\n          text: \"Jos tarvitsisin yll\u00e4tt\u00e4en noin 500 euroa lainaksi, tiet\u00e4isin verkosta jonkun, jolta voisin pyyt\u00e4\u00e4 apua.\"\n        },\n        {\n          id: \"q08\",\n          number: 8,\n          kind: \"likert\",\n          category: \"offlineBridging\",\n          reverse: false,\n          text: \"Arjessa kohtaamani ihmiset muistuttavat minua siit\u00e4, ett\u00e4 ihmiset ja yhteis\u00f6t ovat yhteydess\u00e4 toisiinsa.\"\n        },\n        {\n          id: \"q09\",\n          number: 9,\n          kind: \"likert\",\n          category: \"onlineBonding\",\n          reverse: false,\n          text: \"Verkossa tuntemani ihmiset voisivat toimia minulle uskottavina suosittelijoina ty\u00f6el\u00e4m\u00e4ss\u00e4.\"\n        },\n        {\n          id: \"q10\",\n          number: 10,\n          kind: \"likert\",\n          category: \"onlineBridging\",\n          reverse: false,\n          text: \"Keskustelut verkossa her\u00e4tt\u00e4v\u00e4t minussa uteliaisuutta muita paikkoja ja kulttuureja kohtaan.\"\n        },\n        {\n          id: \"q11\",\n          number: 11,\n          kind: \"likert\",\n          category: \"offlineBridging\",\n          reverse: false,\n          text: \"Arjen kohtaamiset saavat minut tuntemaan, ett\u00e4 kuulun johonkin itse\u00e4ni suurempaan yhteis\u00f6\u00f6n.\"\n        },\n        {\n          id: \"q12\",\n          number: 12,\n          kind: \"likert\",\n          category: \"offlineBonding\",\n          reverse: false,\n          text: \"Arjessani on ihmisi\u00e4, joilta voin kysy\u00e4 neuvoa, kun teen hyvin t\u00e4rkeit\u00e4 p\u00e4\u00e4t\u00f6ksi\u00e4.\"\n        },\n        {\n          id: \"q13\",\n          number: 13,\n          kind: \"likert\",\n          category: \"onlineBonding\",\n          reverse: false,\n          text: \"Verkossa on useita ihmisi\u00e4, joihin luotan ja joilta voin pyyt\u00e4\u00e4 apua ongelmien ratkaisemiseen.\"\n        },\n        {\n          id: \"q14\",\n          number: 14,\n          kind: \"likert\",\n          category: \"offlineBridging\",\n          reverse: false,\n          text: \"Arjessa kohtaamani ihmiset saavat minut kiinnostumaan siit\u00e4, mit\u00e4 oman paikkakuntani tai l\u00e4hipiirini ulkopuolella tapahtuu.\"\n        },\n        {\n          id: \"q15\",\n          number: 15,\n          kind: \"likert\",\n          category: \"offlineBonding\",\n          reverse: false,\n          text: \"Arjessani on ihmisi\u00e4, jotka auttaisivat minua, vaikka se vaatisi heilt\u00e4 merkitt\u00e4v\u00e4n henkil\u00f6kohtaisen uhrauksen.\"\n        },\n        {\n          id: \"q16\",\n          number: 16,\n          kind: \"likert\",\n          category: \"onlineBridging\",\n          reverse: false,\n          text: \"Verkossa kohtaamani ihmiset innostavat minua kokeilemaan uusia asioita.\"\n        },\n        {\n          id: \"q17\",\n          number: 17,\n          kind: \"likert\",\n          category: \"offlineBonding\",\n          reverse: true,\n          text: \"Arjessani ei ole ket\u00e4\u00e4n, jolle tuntisin oloni turvalliseksi puhua hyvin henkil\u00f6kohtaisista asioista.\"\n        },\n        {\n          id: \"q18\",\n          number: 18,\n          kind: \"likert\",\n          category: \"offlineBridging\",\n          reverse: false,\n          text: \"Arjessa kohtaamani ihmiset auttavat minua n\u00e4kem\u00e4\u00e4n oman el\u00e4m\u00e4ni osana laajempaa kokonaisuutta.\"\n        },\n        {\n          id: \"q19\",\n          number: 19,\n          kind: \"likert\",\n          category: \"onlineBonding\",\n          reverse: false,\n          text: \"Verkossa tuntemani ihmiset puolustaisivat minua silloinkin, kun se voisi vaikuttaa heid\u00e4n omaan maineeseensa.\"\n        },\n        {\n          id: \"q20\",\n          number: 20,\n          kind: \"likert\",\n          category: \"offlineBridging\",\n          reverse: false,\n          text: \"Kohtaan arjessani jatkuvasti uusia ihmisi\u00e4.\"\n        },\n        {\n          id: \"q21\",\n          number: 21,\n          kind: \"age\",\n          category: null,\n          reverse: false,\n          optionalForScoring: true,\n          text: \"Mihin ik\u00e4ryhm\u00e4\u00e4n kuulut?\"\n        }\n      ]);\n\n      const DIMENSIONS = Object.freeze({\n        offlineBonding: {\n          label: \"Arjen l\u00e4heiset suhteet\",\n          shortLabel: \"L\u00e4heiset suhteet arjessa\",\n          description: \"Luottamus, neuvot ja k\u00e4yt\u00e4nn\u00f6n tuki ihmisilt\u00e4, jotka tunnet arjessa.\",\n          strength: \"Arjen ihmissuhteistasi n\u00e4ytt\u00e4\u00e4 l\u00f6ytyv\u00e4n luottamusta ja konkreettista tukea.\",\n          focus: \"Arjen l\u00e4heisiss\u00e4 suhteissa voi olla tilaa vahvistaa avun pyyt\u00e4mist\u00e4, luottamusta tai s\u00e4\u00e4nn\u00f6llist\u00e4 yhteytt\u00e4.\",\n          actions: [\n            {\n              title: \"Nime\u00e4 kaksi turvallista ihmist\u00e4\",\n              detail: \"Kirjoita yl\u00f6s kaksi henkil\u00f6\u00e4, joille voisit soittaa vaikeana p\u00e4iv\u00e4n\u00e4. L\u00e4het\u00e4 toiselle lyhyt viesti t\u00e4ll\u00e4 viikolla.\"\n            },\n            {\n              title: \"Pyyd\u00e4 pient\u00e4 apua\",\n              detail: \"Luottamus kasvaa my\u00f6s k\u00e4yt\u00e4nn\u00f6ss\u00e4. Pyyd\u00e4 yhdelt\u00e4 tutulta pieni neuvo tai konkreettinen apu.\"\n            }\n          ]\n        },\n        offlineBridging: {\n          label: \"Arjen avartavat yhteydet\",\n          shortLabel: \"Avartavat yhteydet arjessa\",\n          description: \"Uudet ihmiset, laajemmat n\u00e4k\u00f6kulmat ja kokemus yhteis\u00f6\u00f6n kuulumisesta arjessa.\",\n          strength: \"Arjen kontaktisi n\u00e4ytt\u00e4v\u00e4t tuovan uusia n\u00e4k\u00f6kulmia ja tunnetta laajempaan yhteis\u00f6\u00f6n kuulumisesta.\",\n          focus: \"Arjen verkosto voi t\u00e4ll\u00e4 hetkell\u00e4 olla melko tuttu ja rajattu. Uudet, kevyet kontaktit voisivat avata n\u00e4k\u00f6kulmia.\",\n          actions: [\n            {\n              title: \"Tee yksi mikrokohtaaminen\",\n              detail: \"Vaihda muutama sana naapurin, ty\u00f6kaverin, harrastustutun tai muun tutun kasvon kanssa ilman suorituspainetta.\"\n            },\n            {\n              title: \"Kokeile matalan kynnyksen ryhm\u00e4\u00e4\",\n              detail: \"Valitse yksi tapahtuma, vapaaehtoisteht\u00e4v\u00e4 tai harrastus, jossa kohtaat ihmisi\u00e4 oman tavallisen piirisi ulkopuolelta.\"\n            }\n          ]\n        },\n        onlineBonding: {\n          label: \"Verkon l\u00e4heiset suhteet\",\n          shortLabel: \"L\u00e4heiset suhteet verkossa\",\n          description: \"Luottamus ja k\u00e4yt\u00e4nn\u00f6n tuki ihmisilt\u00e4, joiden kanssa olet yhteydess\u00e4 verkossa.\",\n          strength: \"Verkkoyhteyksiss\u00e4si n\u00e4ytt\u00e4\u00e4 olevan my\u00f6s luottamusta ja mahdollisuutta saada oikeaa tukea.\",\n          focus: \"Verkkokontaktit voivat olla laajoja, mutta tuki tai luottamus ei ehk\u00e4 viel\u00e4 tunnu kovin syv\u00e4lt\u00e4.\",\n          actions: [\n            {\n              title: \"Syvenn\u00e4 yht\u00e4 verkkosuhdetta\",\n              detail: \"Valitse yksi tuttu, jonka kanssa keskustelu tuntuu aidolta. L\u00e4het\u00e4 henkil\u00f6kohtainen viesti tai ehdota lyhytt\u00e4 puhelua.\"\n            },\n            {\n              title: \"Tarjoa konkreettista tukea\",\n              detail: \"Kysy yhdelt\u00e4 verkkotutulta, miss\u00e4 asiassa voisit olla avuksi. Vastavuoroisuus rakentaa luottamusta.\"\n            }\n          ]\n        },\n        onlineBridging: {\n          label: \"Verkon avartavat yhteydet\",\n          shortLabel: \"Avartavat yhteydet verkossa\",\n          description: \"Uudet keskustelut, n\u00e4k\u00f6kulmat ja yhteis\u00f6osallistuminen verkossa.\",\n          strength: \"Verkko n\u00e4ytt\u00e4\u00e4 tuovan sinulle uusia ihmisi\u00e4, ajatuksia ja mahdollisuuksia osallistua.\",\n          focus: \"Verkon k\u00e4ytt\u00f6 ei ehk\u00e4 t\u00e4ll\u00e4 hetkell\u00e4 tuo paljon uusia n\u00e4k\u00f6kulmia tai aktiivista yhteis\u00f6llisyytt\u00e4.\",\n          actions: [\n            {\n              title: \"Osallistu, \u00e4l\u00e4 vain seuraa\",\n              detail: \"Kommentoi kerran viikossa rakentavasti yhteis\u00f6ss\u00e4, jossa keskustellaan sinulle t\u00e4rke\u00e4st\u00e4 aiheesta.\"\n            },\n            {\n              title: \"Laajenna omaa kuplaa\",\n              detail: \"Seuraa tai kuuntele muutamaa ihmist\u00e4, joiden tausta tai n\u00e4k\u00f6kulma poikkeaa omastasi.\"\n            }\n          ]\n        }\n      });\n\n      const app = document.getElementById(\"app\");\n      const progressBar = document.getElementById(\"progress-bar\");\n      \/\/const progressLabel = document.getElementById(\"progress-label\");\n      const progressPercent = document.getElementById(\"progress-percent\");\n      const progressTrack = document.querySelector(\".progress-track\");\n      const statusBanner = document.getElementById(\"status-banner\");\n\n      let csrfToken = null;\n      let state = loadState();\n      let busy = false;\n\n      function defaultState() {\n        return {\n          version: INSTRUMENT_VERSION,\n          step: 0,\n          consent: false,\n          assessmentId: null,\n          backendConnected: null,\n          answers: {},\n          result: null,\n          resultMode: null,\n          analysisError: null\n        };\n      }\n\n      function loadState() {\n        try {\n          const raw = sessionStorage.getItem(STORAGE_KEY);\n          if (!raw) return defaultState();\n          const parsed = JSON.parse(raw);\n          if (parsed.version !== INSTRUMENT_VERSION) return defaultState();\n          return { ...defaultState(), ...parsed, answers: parsed.answers || {} };\n        } catch {\n          return defaultState();\n        }\n      }\n\n      function persistState() {\n        try {\n          sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));\n        } catch {\n          \/\/ Ty\u00f6kalu toimii ilman selaintallennustakin nykyisen sivulatauksen ajan.\n        }\n      }\n\n      function makeId() {\n        if (window.crypto && typeof window.crypto.randomUUID === \"function\") {\n          return window.crypto.randomUUID();\n        }\n        return `local-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n      }\n\n      function escapeHtml(value) {\n        return String(value ?? \"\")\n          .replace(\/&\/g, \"&amp;\")\n          .replace(\/<\/g, \"&lt;\")\n          .replace(\/>\/g, \"&gt;\")\n          .replace(\/\"\/g, \"&quot;\")\n          .replace(\/'\/g, \"&#039;\");\n      }\n\n      function clampNumber(value, min, max, fallback) {\n        const numeric = Number(value);\n        if (!Number.isFinite(numeric)) return fallback;\n        return Math.min(max, Math.max(min, numeric));\n      }\n\n      function setBanner(message = \"\", kind = \"info\") {\n        if (!message) {\n          statusBanner.hidden = true;\n          statusBanner.textContent = \"\";\n          statusBanner.removeAttribute(\"data-kind\");\n          return;\n        }\n        statusBanner.hidden = false;\n        statusBanner.dataset.kind = kind;\n        statusBanner.textContent = message;\n      }\n\n      function updateProgress() {\n        let percent = 0;\n        let label = \"Aloitus\";\n\n        if (state.result) {\n          percent = 100;\n          label = \"Tulokset\";\n        } else if (state.step > QUESTIONS.length) {\n          percent = 100;\n          label = \"Analysoidaan\";\n        } else if (state.step > 0) {\n          percent = Math.round(((state.step - 1) \/ QUESTIONS.length) * 100);\n          \/\/label = `Kysymys ${state.step} \/ ${QUESTIONS.length}`;\n        }\n\n        progressBar.style.width = `${percent}%`;\n        \/\/progressLabel.textContent = label;\n        progressPercent.textContent = `${percent} %`;\n        progressTrack.setAttribute(\"aria-valuenow\", String(percent));\n      }\n\n      function focusMainHeading() {\n        window.requestAnimationFrame(() => {\n          const heading = app.querySelector(\"[data-focus-heading]\");\n          if (heading) heading.focus();\n        });\n      }\n\n      function apiUrl(path) {\n        return `${String(CONFIG.apiBase).replace(\/\\\/$\/, \"\")}${path}`;\n      }\n\n      async function ensureCsrfToken() {\n        if (csrfToken) return csrfToken;\n        if (!CONFIG.csrfEndpoint) return null;\n\n        try {\n          const payload = await fetchJson(CONFIG.csrfEndpoint, { method: \"GET\" }, false);\n          csrfToken = payload?.data?.token || payload?.token || null;\n        } catch {\n          csrfToken = null;\n        }\n        return csrfToken;\n      }\n\n      async function fetchJson(url, options = {}, includeCsrf = true) {\n        const controller = new AbortController();\n        const timer = window.setTimeout(() => controller.abort(), CONFIG.requestTimeoutMs);\n        const method = String(options.method || \"GET\").toUpperCase();\n        const headers = new Headers(options.headers || {});\n        headers.set(\"Accept\", \"application\/json\");\n\n        if (options.body && typeof options.body !== \"string\") {\n          headers.set(\"Content-Type\", \"application\/json\");\n          options.body = JSON.stringify(options.body);\n        }\n\n        if (includeCsrf && ![\"GET\", \"HEAD\", \"OPTIONS\"].includes(method)) {\n          const token = await ensureCsrfToken();\n          if (token) headers.set(\"X-CSRF-Token\", token);\n        }\n\n        try {\n          const response = await fetch(url, {\n            ...options,\n            method,\n            headers,\n            credentials: \"same-origin\",\n            signal: controller.signal\n          });\n\n          const contentType = response.headers.get(\"content-type\") || \"\";\n          const payload = contentType.includes(\"application\/json\")\n            ? await response.json()\n            : null;\n\n          if (!response.ok) {\n            const error = new Error(payload?.error?.message || `Palvelin vastasi virheell\u00e4 ${response.status}.`);\n            error.status = response.status;\n            error.payload = payload;\n            throw error;\n          }\n\n          return payload || {};\n        } finally {\n          window.clearTimeout(timer);\n        }\n      }\n\n      async function createAssessment() {\n        const requestBody = {\n          instrumentVersion: INSTRUMENT_VERSION,\n          locale: \"fi-FI\",\n          consent: true,\n          startedAt: new Date().toISOString(),\n          source: \"coachingcircle.fi\"\n        };\n\n        try {\n          const payload = await fetchJson(apiUrl(\"\/assessments\"), {\n            method: \"POST\",\n            body: requestBody\n          });\n          const data = payload.data || payload;\n          state.assessmentId = String(data.assessmentId || data.id || makeId());\n          state.backendConnected = true;\n          setBanner();\n        } catch (error) {\n          if (!CONFIG.demoFallback) throw error;\n          state.assessmentId = makeId();\n          state.backendConnected = false;\n          setBanner(\"Esikatselutila: backend-yhteytt\u00e4 ei l\u00f6ytynyt. Vastaukset pysyv\u00e4t t\u00e4ss\u00e4 v\u00e4lilehdess\u00e4, ja lopuksi n\u00e4ytet\u00e4\u00e4n paikallinen demotulos.\");\n        }\n      }\n\n      async function saveAnswer(question, value) {\n        state.answers[question.id] = value;\n        persistState();\n\n        if (state.backendConnected === false) return;\n\n        const path = `\/assessments\/${encodeURIComponent(state.assessmentId)}\/answers\/${encodeURIComponent(question.id)}`;\n        const requestBody = {\n          instrumentVersion: INSTRUMENT_VERSION,\n          questionId: question.id,\n          value,\n          answeredAt: new Date().toISOString(),\n          idempotencyKey: `${state.assessmentId}:${question.id}`\n        };\n\n        try {\n          await fetchJson(apiUrl(path), {\n            method: \"PUT\",\n            body: requestBody\n          });\n        } catch (error) {\n          if (!CONFIG.demoFallback) throw error;\n          state.backendConnected = false;\n          setBanner(\"Backend-yhteys katkesi. Jatkat esikatselutilassa, ja vastaukset s\u00e4ilyv\u00e4t vain t\u00e4ss\u00e4 v\u00e4lilehdess\u00e4.\");\n        }\n      }\n\n      function scoredValue(question, rawValue) {\n        const value = Number(rawValue);\n        return question.reverse ? 6 - value : value;\n      }\n\n      function scoreIndex(raw, minimum, maximum) {\n        if (maximum === minimum) return 0;\n        return Math.round(((raw - minimum) \/ (maximum - minimum)) * 100);\n      }\n\n      function calculateScores() {\n        const scoredQuestions = QUESTIONS.filter((question) => question.kind === \"likert\");\n        const missing = scoredQuestions.filter((question) => !Number.isInteger(Number(state.answers[question.id])));\n        if (missing.length > 0) {\n          throw new Error(\"Kaikki pisteytett\u00e4v\u00e4t kysymykset eiv\u00e4t ole valmiita.\");\n        }\n\n        const rawByDimension = {\n          offlineBonding: 0,\n          offlineBridging: 0,\n          onlineBonding: 0,\n          onlineBridging: 0\n        };\n\n        let overallRaw = 0;\n        for (const question of scoredQuestions) {\n          const value = scoredValue(question, state.answers[question.id]);\n          rawByDimension[question.category] += value;\n          overallRaw += value;\n        }\n\n        const dimensions = {};\n        for (const key of Object.keys(rawByDimension)) {\n          const raw = rawByDimension[key];\n          dimensions[key] = {\n            raw,\n            minimum: 5,\n            maximum: 25,\n            index: scoreIndex(raw, 5, 25)\n          };\n        }\n\n        const offlineRaw = rawByDimension.offlineBonding + rawByDimension.offlineBridging;\n        const onlineRaw = rawByDimension.onlineBonding + rawByDimension.onlineBridging;\n\n        return {\n          overall: {\n            raw: overallRaw,\n            minimum: 20,\n            maximum: 100,\n            index: scoreIndex(overallRaw, 20, 100)\n          },\n          channels: {\n            offline: {\n              raw: offlineRaw,\n              minimum: 10,\n              maximum: 50,\n              index: scoreIndex(offlineRaw, 10, 50)\n            },\n            online: {\n              raw: onlineRaw,\n              minimum: 10,\n              maximum: 50,\n              index: scoreIndex(onlineRaw, 10, 50)\n            }\n          },\n          dimensions\n        };\n      }\n\n      function buildLocalFeedback(scores) {\n        const ranked = Object.entries(scores.dimensions)\n          .map(([key, score]) => ({ key, ...score, ...DIMENSIONS[key] }))\n          .sort((a, b) => b.index - a.index);\n\n        const strongest = ranked[0];\n        const secondStrongest = ranked[1];\n        const growth = [...ranked].reverse()[0];\n        const secondGrowth = [...ranked].reverse()[1];\n        const channelGap = scores.channels.offline.index - scores.channels.online.index;\n\n        let balanceSentence = \"Verkko- ja arkiel\u00e4m\u00e4n yhteydet ovat profiilissasi melko l\u00e4hell\u00e4 toisiaan.\";\n        if (channelGap >= 18) {\n          balanceSentence = \"Arjen yhteydet ovat t\u00e4ll\u00e4 hetkell\u00e4 selv\u00e4sti vahvempi voimavara kuin verkkoyhteydet.\";\n        } else if (channelGap <= -18) {\n          balanceSentence = \"Verkkoyhteydet ovat t\u00e4ll\u00e4 hetkell\u00e4 selv\u00e4sti vahvempi voimavara kuin arjen yhteydet.\";\n        }\n\n        const summary = `Vahvin alueesi on ${strongest.label.toLowerCase()}. Eniten kasvuvaraa n\u00e4kyy alueessa ${growth.label.toLowerCase()}. ${balanceSentence} Tulos ei kerro, ett\u00e4 verkostosi olisi hyv\u00e4 tai huono \u2013 se n\u00e4ytt\u00e4\u00e4, mist\u00e4 saat juuri nyt tukea ja uusia n\u00e4k\u00f6kulmia.`;\n\n        const actions = [\n          growth.actions[0],\n          secondGrowth.actions[0],\n          strongest.actions[1]\n        ];\n\n        return {\n          title: \"Sosiaalisten yhteyksiesi profiili\",\n          summary,\n          strengths: [strongest.strength, secondStrongest.strength],\n          focusAreas: [growth.focus, secondGrowth.focus],\n          actions,\n          reflectionQuestion: \"Kenen kanssa voisit ottaa yhden pienen mutta aidon yhteyden seuraavan seitsem\u00e4n p\u00e4iv\u00e4n aikana?\"\n        };\n      }\n\n      function normalizeStringArray(value, fallback) {\n        if (!Array.isArray(value)) return fallback;\n        const cleaned = value\n          .map((item) => typeof item === \"string\" ? item.trim() : \"\")\n          .filter(Boolean)\n          .slice(0, 5);\n        return cleaned.length ? cleaned : fallback;\n      }\n\n      function normalizeActions(value, fallback) {\n        if (!Array.isArray(value)) return fallback;\n        const cleaned = value\n          .map((item) => {\n            if (typeof item === \"string\") {\n              return { title: item.trim(), detail: \"\" };\n            }\n            if (item && typeof item === \"object\") {\n              return {\n                title: String(item.title || \"\").trim(),\n                detail: String(item.detail || item.firstStep || item.why || \"\").trim()\n              };\n            }\n            return null;\n          })\n          .filter((item) => item && item.title)\n          .slice(0, 5);\n        return cleaned.length ? cleaned : fallback;\n      }\n\n      function normalizeBackendScores(candidate, localScores) {\n        if (!candidate || typeof candidate !== \"object\") return localScores;\n\n        const result = typeof window.structuredClone === \"function\" ? window.structuredClone(localScores) : JSON.parse(JSON.stringify(localScores));\n\n        if (candidate.overall) {\n          result.overall.raw = clampNumber(candidate.overall.raw ?? candidate.overall.score, 20, 100, result.overall.raw);\n          result.overall.index = clampNumber(candidate.overall.index, 0, 100, result.overall.index);\n        }\n\n        if (candidate.channels) {\n          for (const channel of [\"offline\", \"online\"]) {\n            if (candidate.channels[channel]) {\n              result.channels[channel].raw = clampNumber(candidate.channels[channel].raw ?? candidate.channels[channel].score, 10, 50, result.channels[channel].raw);\n              result.channels[channel].index = clampNumber(candidate.channels[channel].index, 0, 100, result.channels[channel].index);\n            }\n          }\n        }\n\n        if (candidate.dimensions) {\n          for (const key of Object.keys(result.dimensions)) {\n            if (candidate.dimensions[key]) {\n              result.dimensions[key].raw = clampNumber(candidate.dimensions[key].raw ?? candidate.dimensions[key].score, 5, 25, result.dimensions[key].raw);\n              result.dimensions[key].index = clampNumber(candidate.dimensions[key].index, 0, 100, result.dimensions[key].index);\n            }\n          }\n        }\n\n        return result;\n      }\n\n      function normalizeBackendResult(payload, localScores, localFeedback) {\n        const data = payload?.data || payload || {};\n        const feedback = data.feedback || data.analysis || {};\n        return {\n          scores: normalizeBackendScores(data.scores, localScores),\n          feedback: {\n            title: String(feedback.title || localFeedback.title).trim(),\n            summary: String(feedback.summary || localFeedback.summary).trim(),\n            strengths: normalizeStringArray(feedback.strengths, localFeedback.strengths),\n            focusAreas: normalizeStringArray(feedback.focusAreas || feedback.growthAreas, localFeedback.focusAreas),\n            actions: normalizeActions(feedback.actions, localFeedback.actions),\n            reflectionQuestion: String(feedback.reflectionQuestion || localFeedback.reflectionQuestion).trim()\n          }\n        };\n      }\n\n      async function analyzeAssessment() {\n        const localScores = calculateScores();\n        const localFeedback = buildLocalFeedback(localScores);\n\n        if (state.backendConnected === false) {\n          state.result = { scores: localScores, feedback: localFeedback };\n          state.resultMode = \"preview\";\n          state.analysisError = null;\n          persistState();\n          return;\n        }\n\n        const answerPayload = QUESTIONS.map((question) => ({\n          questionId: question.id,\n          value: state.answers[question.id] ?? null\n        }));\n\n        try {\n          const payload = await fetchJson(\n            apiUrl(`\/assessments\/${encodeURIComponent(state.assessmentId)}\/analyze`),\n            {\n              method: \"POST\",\n              body: {\n                instrumentVersion: INSTRUMENT_VERSION,\n                locale: \"fi-FI\",\n                answers: answerPayload,\n                clientCalculatedScores: localScores,\n                requestedAt: new Date().toISOString()\n              }\n            }\n          );\n\n          state.result = normalizeBackendResult(payload, localScores, localFeedback);\n          state.resultMode = \"ai\";\n          state.analysisError = null;\n        } catch (error) {\n          if (!CONFIG.demoFallback) {\n            state.result = null;\n            state.resultMode = null;\n            state.analysisError = error.message || \"Analyysi ei onnistunut.\";\n          } else {\n            state.backendConnected = false;\n            state.result = { scores: localScores, feedback: localFeedback };\n            state.resultMode = \"preview\";\n            state.analysisError = null;\n            setBanner(\"AI-analyysi ei ollut saatavilla. N\u00e4yt\u00e4mme paikallisen esikatselutuloksen.\");\n          }\n        }\n\n        persistState();\n      }\n\n      function renderIntro() {\n        setBanner(state.backendConnected === false\n          ? \"Esikatselutila: backend-yhteytt\u00e4 ei l\u00f6ytynyt. Vastaukset pysyv\u00e4t t\u00e4ss\u00e4 v\u00e4lilehdess\u00e4.\"\n          : \"\");\n\n        app.innerHTML = `\n          <div>\n            <h2 tabindex=\"-1\" data-focus-heading>Kuinka vahva sosiaalinen tukiverkkosi on?<\/h2>\n            <p class=\"lead\">Sosiaalinen p\u00e4\u00e4oma tarkoittaa ihmissuhteita ja niist\u00e4 syntyvi\u00e4 hy\u00f6tyj\u00e4: tukea, neuvoja, uusia n\u00e4k\u00f6kulmia, mahdollisuuksia ja tunnetta siit\u00e4, ett\u00e4 kuuluu johonkin.<\/p>\n\n            <div class=\"intro-grid\" aria-label=\"Kartoituksen nelj\u00e4 n\u00e4k\u00f6kulmaa\">\n              <div class=\"intro-card\"><strong>Arjen l\u00e4heiset suhteet<\/strong><span>Luottamus, henkil\u00f6kohtainen tuki ja avun saaminen.<\/span><\/div>\n              <div class=\"intro-card\"><strong>Arjen avartavat yhteydet<\/strong><span>Uudet ihmiset, n\u00e4k\u00f6kulmat ja yhteis\u00f6llisyys.<\/span><\/div>\n              <div class=\"intro-card\"><strong>Verkon l\u00e4heiset suhteet<\/strong><span>Luottamus ja tuki digitaalisissa yhteyksiss\u00e4.<\/span><\/div>\n              <div class=\"intro-card\"><strong>Verkon avartavat yhteydet<\/strong><span>Uudet keskustelut ja yhteis\u00f6t verkossa.<\/span><\/div>\n            <\/div>\n\n            <div class=\"note\">\n              <strong>Hyv\u00e4 tiet\u00e4\u00e4:<\/strong> T\u00e4ss\u00e4 ty\u00f6kalussa &quot;arjen suhteet&quot; tarkoittavat suhteita, jotka eiv\u00e4t el\u00e4 vain verkossa. Ik\u00e4ryhm\u00e4 ei vaikuta pisteisiin, ja sen voi j\u00e4tt\u00e4\u00e4 kertomatta. Tulos on suuntaa-antava &ndash; ei diagnoosi eik\u00e4 ihmisarvon mittari.\n            <\/div>\n\n            <div class=\"consent-box\">\n              <input type=\"checkbox\" id=\"consent\" ${state.consent ? \"checked\" : \"\"}>\n              <label for=\"consent\">Ymm\u00e4rr\u00e4n, ett\u00e4 vastaukseni l\u00e4hetet\u00e4\u00e4n Coaching Circlen palvelimelle analyysin tuottamista varten. Olen tutustunut <a href=\"${escapeHtml(CONFIG.privacyUrl)}\" target=\"_blank\" rel=\"noopener noreferrer\">tietosuojailmoitukseen<\/a>.<\/label>\n            <\/div>\n            <p class=\"validation-message\" id=\"intro-error\" role=\"alert\" hidden><\/p>\n\n            <div class=\"button-row\">\n              <span><\/span>\n              <button class=\"button button-primary\" id=\"start-button\" type=\"button\">Aloita kartoitus<\/button>\n            <\/div>\n          <\/div>\n        `;\n\n        const consentInput = document.getElementById(\"consent\");\n        const startButton = document.getElementById(\"start-button\");\n        const introError = document.getElementById(\"intro-error\");\n\n        consentInput.addEventListener(\"change\", () => {\n          state.consent = consentInput.checked;\n          persistState();\n          introError.hidden = true;\n        });\n\n        startButton.addEventListener(\"click\", async () => {\n          if (busy) return;\n          if (!consentInput.checked) {\n            introError.textContent = \"Hyv\u00e4ksy tietojen k\u00e4sittely\u00e4 koskeva kohta ennen aloittamista.\";\n            introError.hidden = false;\n            consentInput.focus();\n            return;\n          }\n\n          busy = true;\n          startButton.disabled = true;\n          startButton.textContent = \"Aloitetaan\u2026\";\n          try {\n            state.consent = true;\n            if (!state.assessmentId) await createAssessment();\n            state.step = 1;\n            persistState();\n            render();\n          } catch (error) {\n            introError.textContent = error.message || \"Kartoitusta ei saatu k\u00e4ynnistetty\u00e4. Yrit\u00e4 hetken kuluttua uudelleen.\";\n            introError.hidden = false;\n            startButton.disabled = false;\n            startButton.textContent = \"Aloita kartoitus\";\n          } finally {\n            busy = false;\n          }\n        });\n      }\n\n      function renderQuestion() {\n        const question = QUESTIONS[state.step - 1];\n        const options = question.kind === \"age\" ? AGE_OPTIONS : LIKERT_OPTIONS;\n        const selectedValue = state.answers[question.id];\n        const context = question.kind === \"age\"\n          ? \"T\u00e4m\u00e4 taustatieto ei vaikuta pisteisiin. Voit valita my\u00f6s \u201cEn halua vastata\u201d.\"\n          : \"Valitse vaihtoehto, joka kuvaa kokemustasi t\u00e4ll\u00e4 hetkell\u00e4 parhaiten.\";\n        const nextLabel = question.number === QUESTIONS.length ? \"N\u00e4yt\u00e4 tulokset\" : \"Jatka\";\n\n        app.innerHTML = `\n          <form id=\"question-form\" novalidate>\n            <h2 id=\"question-title\" tabindex=\"-1\" data-focus-heading><span class=\"question-label\">V\u00e4ite:<\/span> ${escapeHtml(question.text)}<\/h2>\n            <p class=\"question-context\">${escapeHtml(context)}<\/p>\n\n            <fieldset class=\"options\" aria-labelledby=\"question-title\">\n              <legend class=\"sr-only\">Valitse vastaus<\/legend>\n              ${options.map((option, index) => {\n                const id = `${question.id}-option-${index}`;\n                const checked = String(selectedValue) === String(option.value) ? \"checked\" : \"\";\n                return `\n                  <label class=\"option\" for=\"${id}\">\n                    <input id=\"${id}\" name=\"answer\" type=\"radio\" value=\"${escapeHtml(option.value)}\" ${checked}>\n                    <span>${escapeHtml(option.label)}<\/span>\n                  <\/label>\n                `;\n              }).join(\"\")}\n            <\/fieldset>\n\n            <p class=\"validation-message\" id=\"question-error\" role=\"alert\" hidden><\/p>\n            <p class=\"save-state\" id=\"save-state\" role=\"status\" aria-live=\"polite\"><\/p>\n\n            <div class=\"button-row\">\n              <button class=\"button button-secondary\" id=\"back-button\" type=\"button\">Takaisin<\/button>\n              <div class=\"button-group\">\n                <button class=\"button button-primary\" id=\"next-button\" type=\"submit\" ${selectedValue === undefined ? \"disabled\" : \"\"}>${nextLabel}<\/button>\n              <\/div>\n            <\/div>\n          <\/form>\n        `;\n\n        const form = document.getElementById(\"question-form\");\n        const nextButton = document.getElementById(\"next-button\");\n        const backButton = document.getElementById(\"back-button\");\n        const questionError = document.getElementById(\"question-error\");\n        const saveState = document.getElementById(\"save-state\");\n\n        form.addEventListener(\"change\", (event) => {\n          if (event.target.name !== \"answer\") return;\n          const value = question.kind === \"likert\" ? Number(event.target.value) : event.target.value;\n          state.answers[question.id] = value;\n          persistState();\n          nextButton.disabled = false;\n          questionError.hidden = true;\n        });\n\n        backButton.addEventListener(\"click\", () => {\n          if (busy) return;\n          state.step = Math.max(0, state.step - 1);\n          persistState();\n          render();\n        });\n\n        form.addEventListener(\"submit\", async (event) => {\n          event.preventDefault();\n          if (busy) return;\n\n          const selected = form.querySelector('input[name=\"answer\"]:checked');\n          if (!selected) {\n            questionError.textContent = \"Valitse yksi vaihtoehto ennen jatkamista.\";\n            questionError.hidden = false;\n            return;\n          }\n\n          const value = question.kind === \"likert\" ? Number(selected.value) : selected.value;\n          busy = true;\n          nextButton.disabled = true;\n          backButton.disabled = true;\n          nextButton.textContent = question.number === QUESTIONS.length ? \"Tallennetaan\u2026\" : \"Tallennetaan\u2026\";\n          saveState.textContent = \"Tallennetaan vastausta\u2026\";\n\n          try {\n            await saveAnswer(question, value);\n            saveState.textContent = state.backendConnected === false\n              ? \"Tallennettu t\u00e4h\u00e4n v\u00e4lilehteen.\"\n              : \"Tallennettu.\";\n\n            if (question.number === QUESTIONS.length) {\n              state.step = QUESTIONS.length + 1;\n              persistState();\n              render();\n              await analyzeAssessment();\n              render();\n            } else {\n              state.step += 1;\n              persistState();\n              render();\n            }\n          } catch (error) {\n            questionError.textContent = error.message || \"Vastauksen tallentaminen ei onnistunut. Yrit\u00e4 uudelleen.\";\n            questionError.hidden = false;\n            nextButton.disabled = false;\n            backButton.disabled = false;\n            nextButton.textContent = nextLabel;\n            saveState.textContent = \"\";\n          } finally {\n            busy = false;\n          }\n        });\n      }\n\n      function renderAnalysis() {\n        app.innerHTML = `\n          <div class=\"analysis-view\">\n            <div class=\"spinner\" aria-hidden=\"true\"><\/div>\n            <p class=\"eyebrow\">Vastaukset valmiina<\/p>\n            <h2 tabindex=\"-1\" data-focus-heading>Muodostetaan sosiaalisten yhteyksiesi profiilia\u2026<\/h2>\n            <p class=\"lead\">Laskemme nelj\u00e4 osa-aluetta ja kokoamme niiden perusteella k\u00e4yt\u00e4nn\u00f6lliset kehitysehdotukset.<\/p>\n          <\/div>\n        `;\n      }\n\n      function metricCard(label, value, description) {\n        const safeValue = clampNumber(value, 0, 100, 0);\n        return `\n          <div class=\"metric-card\">\n            <div class=\"metric-heading\">\n              <strong>${escapeHtml(label)}<\/strong>\n              <span>${Math.round(safeValue)} \/ 100<\/span>\n            <\/div>\n            <div class=\"meter\" aria-label=\"${escapeHtml(label)} ${Math.round(safeValue)} sadasta\">\n              <span style=\"width:${safeValue}%\"><\/span>\n            <\/div>\n            <p>${escapeHtml(description)}<\/p>\n          <\/div>\n        `;\n      }\n\n      function renderResult() {\n        const { scores, feedback } = state.result;\n        const modeLabel = state.resultMode === \"ai\" ? \"AI-avusteinen palaute\" : \"Paikallinen esikatselutulos\";\n\n        const channelCards = [\n          metricCard(\"Arjen yhteydet\", scores.channels.offline.index, \"L\u00e4heiset ja avartavat suhteet verkon ulkopuolisessa arjessa.\"),\n          metricCard(\"Verkkoyhteydet\", scores.channels.online.index, \"L\u00e4heiset ja avartavat suhteet digitaalisissa ymp\u00e4rist\u00f6iss\u00e4.\")\n        ].join(\"\");\n\n        const dimensionCards = Object.entries(DIMENSIONS).map(([key, meta]) => {\n          return metricCard(meta.label, scores.dimensions[key].index, meta.description);\n        }).join(\"\");\n\n        const strengths = feedback.strengths.map((item) => `<li>${escapeHtml(item)}<\/li>`).join(\"\");\n        const focusAreas = feedback.focusAreas.map((item) => `<li>${escapeHtml(item)}<\/li>`).join(\"\");\n        const actions = feedback.actions.map((item) => `\n          <li>\n            <strong>${escapeHtml(item.title)}<\/strong>\n            ${item.detail ? `<span>${escapeHtml(item.detail)}<\/span>` : \"\"}\n          <\/li>\n        `).join(\"\");\n\n        app.innerHTML = `\n          <div>\n            <div class=\"result-hero\">\n              <div>\n                <p class=\"eyebrow\">Kartoitus valmis<\/p>\n                <h1 tabindex=\"-1\" data-focus-heading>${escapeHtml(feedback.title)}<\/h1>\n                <p class=\"lead\">${escapeHtml(feedback.summary)}<\/p>\n              <\/div>\n              <div class=\"result-score\" aria-label=\"Sosiaalisen p\u00e4\u00e4oman kokonaispisteet ${Math.round(scores.overall.raw)} sadasta\">\n                <strong>${Math.round(scores.overall.raw)}<\/strong>\n                <span>pistett\u00e4 asteikolla 20\u2013100<\/span>\n                <span class=\"result-mode\">${escapeHtml(modeLabel)}<\/span>\n              <\/div>\n            <\/div>\n\n            <section class=\"feedback-section\" aria-labelledby=\"channels-heading\">\n              <h2 id=\"channels-heading\">Arki ja verkko<\/h2>\n              <div class=\"channel-grid\">${channelCards}<\/div>\n            <\/section>\n\n            <section class=\"feedback-section\" aria-labelledby=\"dimensions-heading\">\n              <h2 id=\"dimensions-heading\">Nelj\u00e4 n\u00e4k\u00f6kulmaa<\/h2>\n              <div class=\"dimension-grid\">${dimensionCards}<\/div>\n            <\/section>\n\n            <section class=\"feedback-section\" aria-labelledby=\"strengths-heading\">\n              <h2 id=\"strengths-heading\">Vahvuudet<\/h2>\n              <ul class=\"feedback-list\">${strengths}<\/ul>\n            <\/section>\n\n            <section class=\"feedback-section\" aria-labelledby=\"focus-heading\">\n              <h2 id=\"focus-heading\">Kasvun paikat<\/h2>\n              <ul class=\"feedback-list\">${focusAreas}<\/ul>\n            <\/section>\n\n            <section class=\"feedback-section\" aria-labelledby=\"actions-heading\">\n              <h2 id=\"actions-heading\">Kolme pient\u00e4 kokeilua<\/h2>\n              <ol class=\"action-list\">${actions}<\/ol>\n            <\/section>\n\n            <section class=\"reflection\" aria-labelledby=\"reflection-heading\">\n              <h3 id=\"reflection-heading\">Pohdintakysymys<\/h3>\n              <p>${escapeHtml(feedback.reflectionQuestion)}<\/p>\n            <\/section>\n\n            <p class=\"disclaimer\">Pisteet kuvaavat t\u00e4m\u00e4n hetken kokemustasi. Mittari tarkastelee ihmissuhteista saatavaa tukea ja avartavia yhteyksi\u00e4, ei pelkk\u00e4\u00e4 kontaktien lukum\u00e4\u00e4r\u00e4\u00e4. Tulokselle ei ole t\u00e4ss\u00e4 suomalaisessa mukautuksessa kliinisi\u00e4 raja-arvoja.<\/p>\n\n            <div class=\"button-row\">\n              <button class=\"button button-secondary\" id=\"restart-button\" type=\"button\">Tee kartoitus uudelleen<\/button>\n              <div class=\"button-group\">\n                <button class=\"button button-secondary\" id=\"print-button\" type=\"button\">Tulosta tulos<\/button>\n                <a class=\"button-link\" href=\"${escapeHtml(CONFIG.coachDirectoryUrl)}\">Tutustu coacheihin<\/a>\n              <\/div>\n            <\/div>\n          <\/div>\n        `;\n\n        document.getElementById(\"restart-button\").addEventListener(\"click\", () => {\n          const confirmed = window.confirm(\"Haluatko poistaa t\u00e4m\u00e4n v\u00e4lilehden vastaukset ja aloittaa alusta?\");\n          if (!confirmed) return;\n          sessionStorage.removeItem(STORAGE_KEY);\n          state = defaultState();\n          setBanner();\n          render();\n        });\n\n        document.getElementById(\"print-button\").addEventListener(\"click\", () => window.print());\n      }\n\n      function renderAnalysisError() {\n        app.innerHTML = `\n          <div>\n            <p class=\"eyebrow\">Yhteysvirhe<\/p>\n            <h2 tabindex=\"-1\" data-focus-heading>Analyysi ei valmistunut<\/h2>\n            <p class=\"lead\">${escapeHtml(state.analysisError || \"Palvelimeen ei saatu yhteytt\u00e4.\")}<\/p>\n            <p>Vastauksesi ovat edelleen t\u00e4ss\u00e4 v\u00e4lilehdess\u00e4. Voit yritt\u00e4\u00e4 analyysi\u00e4 uudelleen tai palata viimeiseen kysymykseen.<\/p>\n            <div class=\"button-row\">\n              <button class=\"button button-secondary\" id=\"return-button\" type=\"button\">Palaa viimeiseen kysymykseen<\/button>\n              <button class=\"button button-primary\" id=\"retry-button\" type=\"button\">Yrit\u00e4 analyysi\u00e4 uudelleen<\/button>\n            <\/div>\n          <\/div>\n        `;\n\n        document.getElementById(\"return-button\").addEventListener(\"click\", () => {\n          state.step = QUESTIONS.length;\n          state.analysisError = null;\n          persistState();\n          render();\n        });\n\n        document.getElementById(\"retry-button\").addEventListener(\"click\", async () => {\n          state.analysisError = null;\n          state.step = QUESTIONS.length + 1;\n          persistState();\n          render();\n          await analyzeAssessment();\n          render();\n        });\n      }\n\n      function render() {\n        updateProgress();\n\n        if (state.result) {\n          renderResult();\n        } else if (state.analysisError) {\n          renderAnalysisError();\n        } else if (state.step === 0) {\n          renderIntro();\n        } else if (state.step >= 1 && state.step <= QUESTIONS.length) {\n          if (state.backendConnected === false) {\n            setBanner(\"Esikatselutila: vastaukset s\u00e4ilyv\u00e4t vain t\u00e4ss\u00e4 v\u00e4lilehdess\u00e4.\");\n          } else {\n            setBanner();\n          }\n          renderQuestion();\n        } else {\n          renderAnalysis();\n        }\n\n        focusMainHeading();\n      }\n\n      \/\/ Jos selaimeen j\u00e4i keskener\u00e4inen vanha analyysitila, jatka turvallisesti.\n      if (state.step > QUESTIONS.length && !state.result && !state.analysisError) {\n        state.step = QUESTIONS.length;\n      }\n\n      render();\n    })();\n<\/script>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ilmainen ty\u00f6kalu Coaching Circle Sosiaalisen p\u00e4\u00e4oman kartoitus\u00adty\u00f6kalu Edistyminen: 0 % Mukautettu Dmitri Williamsin Internet Social Capital Scales -mittarista (2006) ja Greater Good Centerin &#8220;Social Capital&#8221; -kyselyst\u00e4. T\u00e4m\u00e4 on suuntaa-antava itsearviointi, ei alkuper\u00e4isten tahojen virallinen suomenkielinen validointi eik\u00e4 terveydenhuollon arvio. &amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;p style=&#8221;max-width:760px;margin:32px auto;padding:20px;background:#fff;border-radius:14px;&#8221;&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;T\u00e4m\u00e4 ty\u00f6kalu tarvitsee JavaScriptin toimiakseen.&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;\/p&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;<\/p>\n","protected":false},"author":1,"featured_media":472,"parent":283,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"page-no-title","meta":{"footnotes":""},"class_list":["post-281","page","type-page","status-publish","has-post-thumbnail","hentry"],"_links":{"self":[{"href":"https:\/\/coachingcircle.fi\/info\/wp-json\/wp\/v2\/pages\/281","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/coachingcircle.fi\/info\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/coachingcircle.fi\/info\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/coachingcircle.fi\/info\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/coachingcircle.fi\/info\/wp-json\/wp\/v2\/comments?post=281"}],"version-history":[{"count":75,"href":"https:\/\/coachingcircle.fi\/info\/wp-json\/wp\/v2\/pages\/281\/revisions"}],"predecessor-version":[{"id":492,"href":"https:\/\/coachingcircle.fi\/info\/wp-json\/wp\/v2\/pages\/281\/revisions\/492"}],"up":[{"embeddable":true,"href":"https:\/\/coachingcircle.fi\/info\/wp-json\/wp\/v2\/pages\/283"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/coachingcircle.fi\/info\/wp-json\/wp\/v2\/media\/472"}],"wp:attachment":[{"href":"https:\/\/coachingcircle.fi\/info\/wp-json\/wp\/v2\/media?parent=281"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}