﻿/* Self-hosted fonts: the SOURCE's typefaces, per the 1:1 brief.
   Instrument Serif (display) + Plus Jakarta Sans (body/UI), both measured on
   homeinstead.co.uk and both SIL Open Font License, so self-hosting is allowed.
   Downloaded by scripts/fetch-fonts.mjs; latin + latin-ext only.

   Self-hosted rather than linked because tietosuoja.html states in writing that
   the browser contacts no external service. A Google Fonts link would silently
   falsify the privacy notice. That is a recorded rule on this project.

   NOTE ON WEIGHT: the source declares its display type at 500, but Instrument
   Serif ships weight 400 ONLY, so 400 is what actually paints there. Matching
   the rendered result rather than the declaration.

   Replaces Fraunces + Inter, which were our identity under the previous brief.
   Those six woff2 files are now unreferenced and can be deleted once this
   direction is settled. */
@font-face{font-family:'Instrument Serif';font-style:italic;font-weight:400;font-display:swap;src:url('fonts/instrument-serif-italic-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Instrument Serif';font-style:italic;font-weight:400;font-display:swap;src:url('fonts/instrument-serif-italic-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Instrument Serif';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/instrument-serif-normal-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Instrument Serif';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/instrument-serif-normal-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:200 800;font-display:swap;src:url('fonts/plus-jakarta-sans-normal-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:200 800;font-display:swap;src:url('fonts/plus-jakarta-sans-normal-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}

/* Toivon kotona
   Chassis: structure, spacing rhythm and geometry adopted from the Home Instead
   capture (../_capture/homeinstead-facts.md). Identity is ours, from
   ../BRAND_VISUAL_RULES.md. Two modules ported from Hemby: the named
   practitioner card and the service matrix. */

:root{
  /* ── identity ──
     Hues from HER OWN LOGO. Roles from the source.

     The logo (an anchor fused with a house, over "Toivon kotona Oy") was
     recovered from her previous site and sampled by scripts/logo-colors.mjs:
       #0C4884  43.2% of the mark   the wordmark and the dark anchor
       #84B4E4   ~10% of the mark   the highlight on anchor and roof
     Every palette before this one came from somewhere that was not hers: the
     first from a British franchise, the second from my own guess at "Nordic
     blue". This one is the client's actual brand asset.

     The ROLE STRUCTURE is the source's, because that is what the page was
     missing. Home Instead reads as one system because it runs a single deep
     primary for every dark ground and every icon, a 10% tint of that SAME
     primary for alternating bands, a near-white card ground, and one accent
     used sparingly and almost only on dark. Ours previously had an accent doing
     six unrelated jobs, which is what read as loud.

     It also settles the trade-dress question from 2026-08-17 on its own terms:
     Home Instead Finland Oy is registered in Espoo in the same service
     category, and none of their hues appear here.

     Contrast, computed not eyeballed:
       ink #12212E on white           16.2:1
       ink-soft #48606F on white       6.6:1
       white on navy #0C4884           9.2:1
       sky #84B4E4 on navy #0C4884     4.2:1  (graphics + large text only)      */
  /* Header height, minus its 1px bottom rule. scroll-padding-top reads this. */
  --hdr-h:104px;
  --navy:#0C4884;
  --navy-deep:#08355F;
  --navy-10:rgba(12,72,132,.09);
  --snow:#F5F8FC;
  --ice:#EAF1FA;
  /* The accent. Previously #E8A33D amber, which was doing six jobs at once
     (header pill, hero CTA, a fully amber audience card, the arc, every bullet
     marker, focus rings) and read as aggressive orange against the blue. The
     source's own discipline is one accent used SPARINGLY and almost only on
     dark grounds. Hers is the logo's light blue, so the whole page now draws
     from two hues out of one mark instead of two unrelated families. */
  --amber:#84B4E4;
  --amber-lift:#A8CCF0;
  --amber-ink:#08355F;
  --ink:#12212E;
  --ink-soft:#48606F;
  --rule:#D4E0EC;
  /* Hero scrim, as tokens rather than nine hardcoded rgba() literals. These were
     rgba(11,26,22,...), a green-black left over from the pine palette, which
     read as a green cast fighting the blue everywhere else. Derived from
     --navy-deep. Opacities are the measured ones: see scripts/check-scrim.mjs. */
  --scrim-0:rgba(7,22,36,0);
  --scrim-16:rgba(7,22,36,.16);
  --scrim-24:rgba(7,22,36,.24);
  --scrim-46:rgba(7,22,36,.46);
  --scrim-56:rgba(7,22,36,.56);
  --scrim-70:rgba(7,22,36,.70);
  --scrim-76:rgba(7,22,36,.76);
  --scrim-86:rgba(7,22,36,.86);
  --scrim-92:rgba(7,22,36,.92);
  --scrim-90:rgba(7,22,36,.90);
  --scrim-94:rgba(7,22,36,.94);
  --scrim-97:rgba(7,22,36,.97);

  /* Geometry: the source's, measured. 1600 inner, 80/80 on tinted bands against
     48/48 on flow sections.
     A previous pass moved this to the Finnish field's 1360 (no site in that
     field exceeds 1408). That is still true and still recorded in
     ../_capture/finnish-field-facts.md, but the brief changed to an explicit
     1:1 of the source, so the source's measure governs. Nothing strands at 2560
     because grounds are full-bleed and only .wrap is capped. */
  --inner:1600px;
  --band:80px;
  --flow:48px;
  --gut:clamp(1.25rem,3.2vw,3rem);

  /* Body scale: the source's 16px at line-height 1.5, giving the 4.5
     display-to-body ratio. See the type block below for the full reversal note. */
  --fs-body:16px;
  --fs-lede:1.08rem;
  --fs-small:.97rem;
  --fs-micro:.92rem;

  /* Reference component tokens, measured off homeinstead.co.uk on 2026-08-17 by
     scripts/capture-components.mjs. See _capture/homeinstead-components/.
     These are GEOMETRY, which is what the 1:1 reproduces. The colour values the
     source paints with are recorded in that capture and deliberately NOT used:
     Home Instead Finland Oy is registered in Espoo and trades in the same
     service category as this client, so their palette and type pairing are not
     ours to wear. Swap --navy/--amber/--snow below if that decision changes;
     nothing else has to move. */
  --card-pad:40px;
  --card-radius:8px;
  --card-gap:24px;
  --icon-size:40px;
  --panel-pad:48px;
  --panel-gap:28px;

  --serif:"Instrument Serif",Georgia,"Times New Roman",serif;
  --sans:"Plus Jakarta Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  /* micro-interaction curve: captured from Home Instead, Tailwind default, 0.15s */
  --ease:cubic-bezier(0,0,.2,1);
  --t:.15s;
  /* entrance curve: easeOutQuart, measured on Hemma (79 uses). See
     ../competitor-visual-benchmark.md `motion:`. The two aspirational sites are
     motion-poor; entrance choreography is adopted from the FIELD, not the bar. */
  --ease-in-view:cubic-bezier(.165,.84,.44,1);
  --t-in-view:.62s;
}
*{box-sizing:border-box}
/* Form controls do NOT inherit font-family from the page; the UA supplies its
   own. .pill set size and weight but never the family, so every button and the
   hero input were rendering in Arial while everything around them was Plus
   Jakarta Sans. Invisible at a glance, obvious once the rendered DOM is walked
   (scripts/check-palette.mjs). */
button,input,select,textarea{font-family:inherit}
/* --hdr-h is the single source for the sticky header's height: .hdr .wrap sets
   it, and scroll-padding-top derives from it. It was previously a hardcoded
   96px against a 105px header, so every in-page #anchor landed 9px UNDERNEATH
   the header. That went unnoticed while anchors were decorative; it stopped
   being cosmetic when seven CTAs started pointing at yhteystiedot.html#lomake.
   Guarded by scripts/check-anchor.mjs at nine widths. */
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;scroll-padding-top:calc(var(--hdr-h) + 1.75rem)}
body{margin:0;background:#fff;color:var(--ink);font-family:var(--sans);font-size:var(--fs-body);line-height:1.5;hyphens:manual}
img{display:block;max-width:100%}
a{color:inherit}
p,li{text-wrap:pretty}
:focus-visible{outline:3px solid var(--amber);outline-offset:3px}
.wrap{max-width:var(--inner);margin:0 auto;padding:0 var(--gut)}

/* Type scale: the source's, our face.
   REVERTED to the source values on 2026-08-17. A previous pass moved this to the
   Finnish field's scale (ratio 2.8, h1 56px, line-height 1.10) on the reasoning
   that the 4.5 ratio is what made the page read translated. The brief changed:
   the structure is now an explicit 1:1 of the source, so the scale goes back.
   Measured, per scripts/capture-components.mjs:
     h1 72/500/lh1.00/-1.8px   h2 60/500/lh1.00/-1.5px
     h3 48/500/lh1.00/normal   h4 36/400/lh1.00/-0.9px   body 16/400/lh1.50
   The FACE is now the source's own, Instrument Serif, per the 1:1 brief. Weight
   is 400 rather than the declared 500: Instrument Serif ships a single weight,
   so 400 is what actually paints on the source too. */
h1,h2,h3,h4{font-family:var(--serif);font-weight:400;line-height:1.00;margin:0;text-wrap:balance}
/* vw coefficients are set so each size lands EXACTLY on the measured value at
   1440, which is the width the source was captured at. The previous
   coefficients undershot: h3 rendered 44.6px against the source's 48. */
h1{font-size:clamp(2.6rem,5vw,4.5rem);letter-spacing:-.025em}
h2{font-size:clamp(2.15rem,4.17vw,3.75rem);letter-spacing:-.025em}
h3{font-size:clamp(1.75rem,3.34vw,3rem);letter-spacing:normal}
h4{font-size:clamp(1.3rem,2.5vw,2.25rem);font-weight:400;letter-spacing:-.025em}
h1 em,h2 em,h3 em{font-style:italic}
p{margin:0 0 1rem}

/* buttons: pill geometry from the capture */
.pill{
  display:inline-flex;align-items:center;justify-content:center;gap:.5rem;border-radius:999px;
  padding:.9rem 1.75rem;font-weight:600;font-size:var(--fs-small);text-decoration:none;cursor:pointer;
  /* Primary action is the DEEP brand colour, not the accent. The accent is a
     highlight, and using it for the main button left every CTA looking like a
     secondary one. White on --navy measures 9.2:1. On the dark hero this flips
     to the light accent, see .hero .pill below, because a navy pill on a navy
     scrim would disappear. */
  background:var(--navy);color:#fff;border:1.5px solid var(--navy);
  transition:background var(--t) var(--ease),color var(--t) var(--ease),border-color var(--t) var(--ease);
}
.pill:hover{background:var(--navy-deep);border-color:var(--navy-deep)}

.pill.dark{background:var(--navy);color:var(--snow);border-color:var(--navy)}
.pill.dark:hover{background:var(--navy-deep);border-color:var(--navy-deep)}
.pill.ghost{background:transparent;color:var(--navy);border-color:var(--navy)}
.pill.ghost:hover{background:var(--navy);color:var(--snow)}
.pill.on-dark{background:transparent;color:var(--snow);border-color:rgba(246,241,231,.5)}
.pill.on-dark:hover{background:var(--snow);color:var(--navy);border-color:var(--snow)}
.arrowbtn{
  width:44px;height:44px;border-radius:999px;border:1.5px solid var(--navy);flex:none;
  background:transparent;color:var(--navy);display:grid;place-items:center;cursor:pointer;
  transition:background var(--t) var(--ease),color var(--t) var(--ease);
}
.arrowbtn:hover{background:var(--navy);color:var(--snow)}
.arrowbtn svg{width:18px;height:18px}
.on-navy .arrowbtn{border-color:rgba(246,241,231,.5);color:var(--snow)}
.on-navy .arrowbtn:hover{background:var(--snow);color:var(--navy)}

/* header */
/* Header: white, 104px (the source's 56px content row inside 24/24 padding),
   not the tinted 84px bar that was here. */
.hdr{background:#fff;position:sticky;top:0;z-index:60;border-bottom:1px solid var(--rule)}
.hdr .wrap{display:flex;align-items:center;gap:1.4rem;height:var(--hdr-h)}
/* Brand lockup. The mark is her real logo, cropped out of the stacked
   logo+wordmark file by scripts/prep-assets.mjs. Cropping a mark off its own
   lockup is normal usage; recolouring it would not be, and is forbidden here.
   That is also why the FOOTER keeps a text wordmark instead of this image: the
   logo is dark blue on white and the footer ground is navy, and the fix for
   that is never a filter over a client's logo. */
.mark{display:inline-flex;align-items:center;gap:.65rem;font-family:var(--serif);font-size:1.55rem;text-decoration:none;line-height:1;white-space:nowrap}
.mark img{height:42px;width:auto;flex:none}
.mark b{font-weight:400}
footer .mark img{display:none}
.nav{margin-left:auto;display:flex;gap:1.9rem;font-size:var(--fs-micro)}
.nav a{text-decoration:none}
.nav a:hover,.nav a[aria-current=page]{text-decoration:underline;text-underline-offset:6px;text-decoration-color:var(--amber);text-decoration-thickness:2px}
.hdr-act{display:flex;align-items:center;gap:1.2rem}
.tel{display:inline-flex;align-items:center;gap:.5rem;text-decoration:none;font-weight:600;white-space:nowrap}
.tel svg{width:17px;height:17px;flex:none;opacity:.7}
.burger{display:none;background:none;border:0;color:var(--ink);padding:.5rem;margin-left:auto}

/* ── hero ──
   Full-bleed photograph with the type over it. Both competent sites in the
   Finnish field do exactly this (Hemby 740px, Hemma 640px) and both put a chip
   above the h1. The split hero this replaces existed so type never sat on a
   photograph, which was a sound instinct and is the thing that now reads
   foreign. Field heroes run 640-740px, so we sit in that band.

   The scrim is MEASURED, not guessed: white-on-photo can score 100 in
   Lighthouse and still fail AA, because Lighthouse cannot see the pixels under
   the text. scripts/check-scrim.mjs composites this gradient over the real
   image and reports the worst-case ratio in the text box at each breakpoint. */
/* REBUILT to the source's actual construction, measured by
   scripts/capture-hero.mjs. The previous full-bleed-photo-with-type-over-it was
   from the superseded "read Finnish" brief and is not what the source does.

   Source, at 1440:
     section  1440x650, cream ground
     left     678x650, padding 0 80px, text vertically centred
     h1       144px tall = two lines at 72px
     form     518x72 white pill, radius 999, padding 8/8/8/32
       input  300x56          button 121x56 accent pill, radius 999
     right    762x650 photograph, clip-path
              polygon(35% 0, 100% 0, 100% 100%, 0 100%, 0 30%)

   That polygon is the diagonal: it cuts the photo panel's top-left corner from
   35% along its top edge down to 30% down its left edge. It is a clip-path, not
   a border, a skew or an image with a baked-in angle. */
.hero{background:var(--snow);color:var(--ink);overflow:hidden}
/* 678 / 762 at 1440 is the source's split, expressed as a ratio so it holds at
   any width rather than only at the capture width. */
.hero-grid{display:grid;grid-template-columns:678fr 762fr;min-height:650px;align-items:stretch}
.hero-media{position:relative;clip-path:polygon(35% 0,100% 0,100% 100%,0 100%,0 30%);background:var(--ice)}
.hero-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:50% 42%}
/* Text panel. 80px inset at 1440, matching the source's padding 0/80, scaled
   down on narrow viewports so it does not eat the measure. */
.hero-inner{
  display:flex;flex-direction:column;justify-content:center;max-width:none;
  padding:clamp(2.5rem,5vw,4rem) clamp(1.5rem,5.5vw,80px);
}
/* Reference chip, above the h1. Felix, discovery call 00:32:21: "Toi tähän ylös,
   heti sä tuut tähän sivulle, heti tähän joku lause siitä, mitä hän sanoi susta
   ja sitten ehkä kuva hänestä." Note ylös, first viewport, not mid-page. It is
   also the Finnish field's own pattern: both competent competitors put a rating
   chip in exactly this slot.
   Built now, rendered only when a real quote exists. The markup sits commented
   out in index.html rather than stubbed, because a visible empty chip is worse
   than no chip. Degrades to text-only: Felix confirmed a photo is optional
   (today's call, 00:22:00). */
.refchip{
  display:inline-flex;align-items:center;gap:.7rem;margin:0 0 1.5rem;
  padding:.45rem .95rem .45rem .45rem;border-radius:999px;
  background:#fff;border:1px solid var(--rule);max-width:52ch;
}
.refchip img{width:32px;height:32px;border-radius:50%;object-fit:cover;flex:none}
.refchip:not(:has(img)){padding-left:.95rem}
.refchip q{font-size:var(--fs-micro);line-height:1.35;text-wrap:balance;quotes:"\201D" "\201D"}
.refchip cite{font-style:normal;font-size:.78rem;color:var(--ink-soft);white-space:nowrap}
.hero h1{color:var(--ink)}
.hero .lede{color:var(--ink-soft);max-width:46ch}
.hero .hero-note{color:var(--ink-soft)}
.hero .arc path{stroke:var(--amber)}

/* The source's hero holds a 72px white pill containing a postcode field and a
   56px button. We reproduced it exactly and then removed it, because their
   field routes a postcode to one of ~200 franchise offices and ours collected a
   phone number for a callback nobody had committed to answering. Reproducing a
   component whose JOB does not exist here is not fidelity, it is cargo cult.
   Full reasoning in the comment at the hero in index.html.
   Two pills instead: phone first, then the low-threshold path, which is the
   shape Hemby uses and Hemby runs no forms at all. */
.hero-cta{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:1.9rem}
/* Founder byline: small round portrait beside the CTA. object-position favours
   the top of the frame for the same reason the story portrait does, so a circle
   crop never takes the top of her head off. */
.byline{display:flex;align-items:center;gap:.9rem;margin-top:1.6rem}
/* Square, face-centred source (scripts/make-avatar.mjs), so object-position has
   nothing left to correct. The old 4:5 portrait covered on WIDTH, which made the
   X value inert and left her face 5px left of the circle. */
.byline img{width:56px;height:56px;border-radius:50%;object-fit:cover;flex:none}
.byline p{margin:0;font-size:var(--fs-micro);line-height:1.4;color:var(--ink-soft)}
.byline b{display:block;color:var(--ink);font-weight:700;font-size:1rem}

/* Stack below 1000. The diagonal is dropped rather than rotated: at a stacked
   width it would cut across the photograph's subject instead of reading as an
   edge between two panels. */
@media (max-width:1000px){
  .hero-grid{grid-template-columns:1fr;min-height:0}
  .hero-media{clip-path:none;min-height:320px;order:-1}
  .finder{padding:8px;flex-wrap:wrap}
  .finder input{padding-left:1.25rem}
}
.eyebrow{display:block;font-size:.8rem;letter-spacing:.12em;text-transform:uppercase;font-weight:600;color:var(--navy);opacity:.8;margin-bottom:1.3rem}
.lede{font-size:var(--fs-lede);max-width:46ch;margin-top:1.6rem;color:var(--ink-soft)}
.arc{display:block;width:clamp(150px,15vw,240px);margin:.8rem 0 0;overflow:visible}
.arc.center{margin:1.5rem auto 0}
.arc path{fill:none;stroke:var(--amber);stroke-width:2.6;stroke-linecap:round}
.finder{display:flex;gap:.6rem;margin-top:1.9rem;max-width:34rem;flex-wrap:wrap}
.finder input{flex:1 1 15rem;min-width:0;border:1.5px solid rgba(28,58,50,.28);border-radius:999px;padding:.9rem 1.4rem;font:inherit;background:#fff}
.finder input:focus{outline:none;border-color:var(--navy)}
/* Sits directly under the hero CTA pair and states what the primary button
   actually buys. The button says "Varaa ensikaynti"; without this the reader has
   to reach hinnasto.html to learn what a first visit costs and whether it binds
   them. Price is bolded, the no-commitment clause carries equal weight. */
.hero-offer{margin:1.15rem 0 0;font-size:var(--fs-micro);color:var(--ink-soft);max-width:52ch;text-wrap:pretty}
.hero-offer b{color:var(--ink);font-weight:700}
.hero-note{margin:1.1rem 0 0;font-size:var(--fs-micro);color:var(--ink-soft)}
.hero-media{position:relative;border-radius:12px;overflow:hidden;aspect-ratio:4/3;background:var(--ice)}
.hero-media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}

/* Trust strip.
   This was an infinite scrolling marquee copied from the source. It moved the
   same four claims that already appear elsewhere on the page, needed its DOM
   duplicated (and the clones hidden from screen readers) to loop, and needed a
   WCAG 2.2.2 pause mechanism to be permissible at all. A static strip says the
   same things, costs no motion, duplicates nothing, and cannot fail 2.2.2. */
/* Stat band, 1:1 with the source: a dark full-bleed strip directly under the
   hero, 104px tall (padding 32/0), cells separated by VERTICAL RULES rather
   than the dot bullets that were here. The source scrolls it as a marquee; ours
   is static, because a marquee needs its DOM duplicated, needs the clones hidden
   from screen readers, and needs a WCAG 2.2.2 pause control to be permissible at
   all. Four claims that fit do not need any of that. */
.marquee{background:var(--navy);color:#fff;padding:32px 0}
.marquee ul{
  display:flex;flex-wrap:wrap;justify-content:center;align-items:center;
  gap:.9rem 0;margin:0;padding:0;list-style:none;font-size:var(--fs-small);
}
.marquee li{
  padding:0 clamp(1rem,2.6vw,2.75rem);text-align:center;
  border-left:1px solid rgba(255,255,255,.28);
}
.marquee li:first-child{border-left:0}
@media (max-width:760px){
  .marquee li{border-left:0;padding:0 1rem;flex:1 1 100%}
}

/* Credential badge, in the header beside the logo. That is exactly where the
   source puts its Trustist rating: divider rule, small label, emphasised value.
   Theirs reads "TRUSTIST 4.9/5.0". She has no rating yet and one will not be
   invented, so ours carries the credential, which is verified in ANCHOR §5. It
   becomes a real review badge the day the Vantaa testimonial lands. */
.cred{
  display:flex;flex-direction:column;gap:.05rem;margin:0 0 0 1.1rem;padding-left:1.1rem;
  border-left:1px solid var(--rule);font-size:.72rem;line-height:1.35;
  color:var(--ink-soft);white-space:nowrap;letter-spacing:.02em;
}
.cred b{color:var(--navy);font-weight:700;font-size:.84rem;letter-spacing:0}
@media (max-width:900px){ .cred{display:none} }

/* rhythm: captured 80/80 tinted vs 48/48 flow */
.band{background:var(--navy-10);padding:var(--band) 0}
.band.white{background:#fff}
.band.snow{background:var(--snow);padding:120px 0}
.band.navy{background:var(--navy);color:var(--snow)}
.flow{padding:var(--flow) 0}
.sec-head{display:flex;align-items:flex-end;justify-content:space-between;gap:2rem;margin-bottom:2.6rem}
.sec-head.center{justify-content:center;text-align:center;flex-direction:column;align-items:center}
.sec-head p{max-width:54ch;margin:1.1rem 0 0;color:var(--ink-soft)}
.band.navy .sec-head p{color:var(--snow);opacity:.85}
.railnav{display:flex;gap:.6rem}

/* audience cards, colour-coded by importance not category (one-accent rule) */
.svc3{display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem}
.svc3.four{grid-template-columns:repeat(2,1fr)}
.svc{border-radius:8px;padding:1.9rem;min-height:308px;display:flex;flex-direction:column;background:var(--ice);border:1px solid var(--rule)}
/* These cards sit directly under the section h2, so they are h3, not h4. Kept at
   the old h4 size so the correction is semantic only and changes nothing visually. */
.svc h3{font-size:clamp(1.35rem,1.9vw,1.7rem);letter-spacing:-.018em}
.svc.lead{background:var(--amber);border-color:var(--amber)}
.svc p{font-size:var(--fs-small);margin-top:.9rem;color:var(--ink-soft)}
.svc.lead p{color:var(--amber-ink);opacity:.9}
.svc .go{margin-top:auto;display:flex;align-items:center;gap:.8rem;font-weight:600;font-size:var(--fs-micro);text-decoration:none;padding-top:1.5rem}
.svc.lead .arrowbtn{border-color:var(--amber-ink);color:var(--amber-ink)}
.svc.lead .arrowbtn:hover{background:var(--amber-ink);color:var(--amber)}

/* Service cards.
   These were a scroll-snap carousel with prev/next arrows, copied from a source
   that has dozens of services. Measured on our page: scrollWidth equals clientWidth
   at both 2560 and 1440, so all four cards already fit and the arrows controlled
   nothing. Four items that fit are a grid, not a carousel. It becomes a rail only
   below the width where they genuinely stop fitting. */
/* Card rail, 1:1 with the source: fixed 380px cards, 24px gap, horizontal
   scroll with working prev/next.
   A previous pass replaced this with a 4-up grid, correctly, because the cards
   were then flexible and all four fitted, so the arrows controlled nothing.
   Adopting the source's 380px card width changes that arithmetic: 4x380 + 3x24
   is 1592, which exceeds the inner measure at 1440 (1348) AND at 2560 (1504),
   so the overflow is real at every width and the controls do something again.
   Verified rather than assumed, by scripts/check-rail.mjs, which measures the
   RAIL's scrollWidth against its clientWidth. That is a different question from
   the page overflow check-pages.mjs measures; neither substitutes for the other.
   Measured: 88px of overflow at 2560, 244px at 1440, 933px at 360. */
/* CORRECTION to the note above. Fixing the cards at 380px made the row overflow
   by only 88px at 2560, which does not read as a deliberate rail: it reads as
   the last card being sliced off by the window, which is exactly what it looked
   like. A sliver of overflow is a bug, not an affordance.
   So: a real 4-up grid wherever four cards fit, and the rail only below 1200
   where the overflow is large enough to be legible as "there is more, scroll".
   380px stays the CAP, not the fixed width. check-rail.mjs enforces that the
   controls appear only where the overflow is real. */
/* A scroll-snap rail at EVERY width, not a grid that reflows.
   With five cards the old repeat(4) grid orphaned the fifth on its own row at
   >=1200px. The rail is the construction this project adopted from the source
   (measured 380px card, prev/next controls), and check-rail.mjs asserts the
   controls appear exactly when real overflow exists. */
.rail{
  display:flex;overflow-x:auto;scroll-snap-type:x mandatory;
  scroll-behavior:smooth;padding-bottom:.5rem;scrollbar-width:none;
  gap:var(--card-gap);
}
.rail>*{scroll-snap-align:start;flex:0 0 clamp(260px,78vw,380px)}
.rail::-webkit-scrollbar{display:none}
/* clamped against the viewport so a 380px card cannot set a min-content width
   wider than a 360px screen, which is the overflow class this project has
   already shipped once. */
/* Visibility is driven by measured overflow in reveal.js, not by a width
   breakpoint, so the controls can never claim to scroll something that fits. */
.railnav{display:flex}
.railnav[hidden]{display:none}
/* Service card, rebuilt 1:1 against the measured source component
   (_capture/homeinstead-components/, capId 1, 5 instances at 380x472).
   Every number here was read off the live document, not estimated:
     padding 40   radius 8   gap 24   border none   shadow none
     icon 40x40, BARE, filled in the ink colour, sitting directly on the card
     h3 48px / weight 500 / line-height 1.00 / tracking normal
     list items 16px / 400 / line-height 1.50, NO bullet markers
     hover inverts the whole card, 0.15s cubic-bezier(0.4,0,0.2,1)
   The previous version got every one of those wrong: 27px padding, a 1px
   border, a 42px rounded tile behind a 22px icon, and square bullet markers.
   The tile was the biggest error. The source has no tile at all; what looked
   like one is the wrapper sharing the card's own background. */
.scard{
  background:var(--ice);border-radius:var(--card-radius);padding:var(--card-pad);
  display:flex;flex-direction:column;gap:var(--card-gap);
  /* No explicit height. As a flex item this card stretches to the tallest in
     the rail via the default align-items:stretch. height:100% would resolve
     against an auto-height parent and collapse it to content height, which is
     what made the row ragged when a taller card was added. */
  transition:background-color var(--t) cubic-bezier(.4,0,.2,1),color var(--t) cubic-bezier(.4,0,.2,1);
}
.scard{text-decoration:none;color:inherit}
/* Single-word titles. "Terveys ja vointi" wrapped to two lines at 48px inside a
   ~278px measure while "Koti ja arki" stayed on one, so the four cards had
   different internal rhythm and the row read as broken. The source's own labels
   are terse for the same reason. */
.scard h3{text-wrap:balance}
.scard .icon{width:var(--icon-size);height:var(--icon-size);display:block;color:var(--navy);background:none;border-radius:0;margin:0}
.scard .icon svg{width:100%;height:100%;display:block}
.scard h3{margin:0}
.scard ul{margin:0;padding:0;list-style:none;font-size:1rem;line-height:1.5;color:inherit}
.scard li{padding:0;display:block}
/* The card inverts on hover. This is the source's signature interaction and the
   single thing that made its grid feel alive; ours had a translateY nudge
   instead, which is the generic default it explicitly is not. */
.scard:hover{background:var(--navy);color:var(--snow)}
.scard:hover .icon{color:var(--amber)}

/* PORTED FROM HEMBY: the named practitioner */
.person{display:grid;grid-template-columns:.8fr 1.2fr;gap:clamp(2rem,4.5vw,5rem);align-items:center}
.person .slot{position:relative;border-radius:12px;overflow:hidden;background:var(--ice);aspect-ratio:4/5}
/* object-position favours the upper part of the frame. The file is a 4:5 crop
   with her face in the top half, so a centred cover crop takes the head off the
   moment the slot gets shorter than 4:5. This is a photograph of a real, named
   person on a healthcare page; a clipped face is not a cosmetic issue. */
.person .slot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:50% 22%}
.person .role{font-size:.78rem;letter-spacing:.12em;text-transform:uppercase;font-weight:600;color:var(--amber);margin:0 0 1rem}
.person blockquote{margin:0 0 1.5rem;font-family:var(--serif);font-weight:400;font-size:clamp(1.5rem,2.1vw,2.05rem);line-height:1.2;letter-spacing:-.02em}
.person .sig{margin:0;font-weight:600}
.person .sig span{display:block;font-weight:400;color:var(--ink-soft);font-size:var(--fs-small);margin-top:.25rem}
/* Her story. Set at the body size in the body face, not as another pull quote:
   the blockquote above it is the claim, this is the person behind it, and two
   display-face blocks stacked would read as two headlines arguing. */
.story{margin-top:1.5rem;max-width:56ch}
.story p{color:var(--ink-soft);margin:0 0 .9rem}
.story p:last-child{margin-bottom:0}
.person .callrow{display:flex;flex-wrap:wrap;gap:1rem 1.6rem;align-items:center;margin-top:2rem;padding-top:1.7rem;border-top:1px solid var(--rule)}
.person .callrow .tel{font-family:var(--serif);font-size:1.6rem}

/* Editorial card, 1:1 with the source (capId 2, 4 instances at 880x488):
     card    880x488, radius 8, near-white ground, NO border, NO shadow
     layout  2-column grid, image half 440x488
     h3      48px with an italic emphasis <span> inside it
     motion  0.15s cubic-bezier(0,0,.2,1)
   Replaces a 3-column image / card / image arrangement that was mine, not the
   source's. The card's own padding measured 0, so the inset lives on the text
   half; 48px is taken from the sibling panel rather than measured directly and
   is the one inferred number here. */
.why{display:flex;justify-content:center}
.ecard{
  display:grid;grid-template-columns:1fr 1fr;width:100%;max-width:880px;min-height:488px;
  background:var(--snow);border-radius:var(--card-radius);overflow:hidden;
  transition:background-color var(--t) var(--ease);
}
.ecard-text{padding:var(--panel-pad);display:flex;flex-direction:column;justify-content:center}
.ecard-text p{color:var(--ink-soft);margin-top:1.1rem;font-size:var(--fs-small)}
.ecard-media{position:relative;background:var(--navy-10)}
.ecard-media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease-in-view)}
.ecard:hover .ecard-media img{transform:scale(1.022)}

/* Full-width editorial image band. The four subpages had no imagery at all and
   read as walls of text; this is the primitive that fixes that. */
.figband{margin:0;border-radius:12px;overflow:hidden;position:relative;aspect-ratio:16/7;background:var(--ice)}
.figband img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease-in-view)}
.figband:hover img{transform:scale(1.022)}
@media (max-width:700px){ .figband{aspect-ratio:16/10} }

/* Verified-credentials list, used inside the pine panel */
.trust{list-style:none;margin:1.6rem 0 0;padding:0;font-size:var(--fs-small)}
.trust li{display:flex;gap:.75rem;padding:.42rem 0;opacity:.94}
.trust li::before{content:"";flex:none;width:7px;height:7px;margin-top:.62em;background:var(--amber)}
.testi .role{font-size:.78rem;letter-spacing:.12em;text-transform:uppercase;font-weight:600;color:var(--amber);margin:0 0 1rem}
.testi .slot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}

/* testimonial panel */
/* Quote panel, 1:1 with the source (capId 4, 2 instances at 720x720):
     panel   720x720, padding 48, radius 0, deep ground
     layout  flex column, gap 28, items-start, justify-center
     mark    40x40 accent glyph above the quote
     quote   Plus Jakarta Sans 30px / 400 / lh 1.20, white
   Note the quote is the SANS at 30px, not the serif. Ours had it in the serif,
   which is a different voice: the source uses its display face for headings and
   keeps testimony in the body face, so the words read as someone speaking
   rather than as another headline. Radius 0 is theirs too; ours had 12. */
/* The source renders TWO of these 720x720 panels side by side, which is why the
   capture reports count 2. A single panel stretched to the full measure was my
   misreading. Ours pairs the quote square with a photograph square, so the row
   is two 720 squares like the source's, with honest content in each. */
.testi-row{display:grid;grid-template-columns:1fr 1fr;align-items:stretch}
/* ONE panel instead of two. Eila's is withheld pending her consent (the reason is
   in index.html at that spot). A lone `aspect-ratio:1` square in a `1fr 1fr` grid
   sits in the left half and strands the right, which is the same orphaned-card
   defect this project already shipped once in the service rail.
   Driven by :has() rather than a class on purpose: put her panel back and the
   two-column layout returns on its own, with no CSS to remember to undo. The
   square ratio is dropped too, because one square at full width would be 1600px
   tall; the measure cap keeps the line length readable at that width. */
.testi-row:has(> .testi:only-child){grid-template-columns:1fr}
.testi-row:has(> .testi:only-child) .testi{aspect-ratio:auto;min-height:0;padding:calc(var(--panel-pad) * 1.2)}
.testi-row:has(> .testi:only-child) blockquote{max-width:32ch}
.testi-media{position:relative;background:var(--navy-deep);overflow:hidden}
.testi-media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease-in-view)}
.testi-media:hover img{transform:scale(1.022)}
.testi{
  background:var(--navy);color:#fff;border-radius:0;padding:var(--panel-pad);
  display:flex;flex-direction:column;align-items:flex-start;justify-content:center;
  gap:var(--panel-gap);aspect-ratio:1;min-height:0;
}
@media (max-width:900px){
  .testi-row{grid-template-columns:1fr}
  .testi{aspect-ratio:auto;min-height:460px}
  .testi-media{min-height:300px}
}
.testi .qmark{width:40px;height:40px;color:var(--amber);flex:none}
.testi .qmark svg{width:100%;height:100%;display:block}
.testi blockquote{
  margin:0;font-family:var(--sans);font-weight:400;
  font-size:clamp(1.35rem,2.1vw,1.875rem);line-height:1.20;letter-spacing:normal;max-width:34ch;
}
.testi cite{display:block;margin-top:1.4rem;font-style:normal;font-size:var(--fs-micro);opacity:.85;font-family:var(--sans)}
.testi .slot{border-radius:8px;overflow:hidden;background:var(--navy-deep);aspect-ratio:1;position:relative;display:grid;place-items:center;text-align:center;padding:1.5rem;font-size:.85rem;color:rgba(246,241,231,.75)}
/* Two real suosittelut landed 2026-08-20, so the pair below finally renders.
   Three reconciliations, all of them small:

   1. figure/figcaption is the W3C attribution pattern for a blockquote, and the
      panel above was authored against a plain container. The flex child is now
      the figcaption, so the cite's own top margin would double the 28px gap.
   2. The second square is navy-deep, the tone .testi-media used. Two identical
      navy squares butted together read as one 1440-wide rectangle with no seam,
      which is not the source's two-panel construction, it is a mistake that
      happens to compile.
   3. The accent glyph is an inline SVG, not the serif quotation mark. Measured:
      the type version overflowed its own flex box by 30px upward and left a
      60px hole under itself, because the glyph sits high in the em box and no
      single line-height value fixes both ends. An SVG has exact bounds, and the
      rule above already anticipated one (`.testi .qmark svg`). */
.testi{margin:0}
.testi figcaption{margin:0}
.testi figcaption cite{margin-top:0}
.testi.deep{background:var(--navy-deep)}
.testi .qmark{width:40px;height:28px}

/* One quote alone on ammattilaisille.html. The 1:1 square is the source's
   two-panel construction and a single square in a full-width wrap is neither
   that nor anything else. */
.testi.wide{aspect-ratio:auto;min-height:0;padding:clamp(2rem,4vw,3.5rem)}
.testi.wide blockquote{max-width:52ch}

/* The chip is real content now, so it has to survive 360. Two failures at that
   width, both from rules that were fine while the chip was a comment:
   52ch of micro type is ~380px against a 320px content box, and the cite's
   `white-space:nowrap` refuses to give the quote any room to wrap beside it.
   Below 520 the pill stacks and stops being a pill. */
.refchip{max-width:min(52ch,100%)}
@media (max-width:520px){
  .refchip{flex-direction:column;align-items:flex-start;gap:.3rem;border-radius:14px;padding:.7rem .9rem}
  .refchip cite{white-space:normal}
}


/* PORTED FROM HEMBY: service matrix */
.matrix{display:grid;grid-template-columns:repeat(3,1fr);border:1px solid var(--rule);background:#fff;border-radius:8px;overflow:hidden}
.mcol{padding:clamp(1.7rem,2.3vw,2.3rem);display:flex;flex-direction:column;border-right:1px solid var(--rule)}
.mcol:last-child{border-right:0}
.mcol.lead{background:var(--navy);color:var(--snow)}
.mcol h3{font-size:1.6rem;margin-bottom:.6rem}
.mcol .fig{font-family:var(--serif);font-size:2.1rem;margin:0 0 1.3rem;letter-spacing:-.02em;line-height:1.15}
.mcol .fig small{font-family:var(--sans);font-size:var(--fs-micro);display:block;color:var(--ink-soft);letter-spacing:0;margin-top:.35rem;font-weight:400}
.mcol.lead .fig small{color:var(--snow);opacity:.78}
.mcol ul{margin:0 0 1.9rem;padding:0;list-style:none;font-size:var(--fs-small)}
.mcol li{display:flex;gap:.6rem;margin-bottom:.72rem;color:var(--ink-soft)}
.mcol.lead li{color:var(--snow);opacity:.92}
.mcol li::before{content:"";flex:none;width:7px;height:7px;margin-top:.55em;background:var(--amber)}
.mcol .pill{margin-top:auto}

/* FAQ. Replaces the nine-pill chip band, which was the most template-shaped
   surface on the page: it posed the questions a worried relative actually has
   and then answered none of them. Split layout so it does not repeat the
   full-width stacked shape of its neighbours. */
.faq{display:grid;grid-template-columns:.72fr 1.28fr;gap:clamp(2rem,5vw,5.5rem);align-items:start}
.faq-aside h2{margin-bottom:1.2rem}
.faq-aside p{color:var(--ink-soft);max-width:38ch;margin-bottom:1.6rem}
.faq-list{border-top:1px solid var(--rule)}
.faq-list details{border-bottom:1px solid var(--rule)}
.faq-list summary{
  display:flex;align-items:flex-start;gap:1.2rem;cursor:pointer;list-style:none;
  padding:1.55rem 0;font-family:var(--serif);font-size:clamp(1.2rem,1.55vw,1.45rem);
  line-height:1.25;letter-spacing:-.015em;transition:color var(--t) var(--ease);
}
.faq-list summary::-webkit-details-marker{display:none}
.faq-list summary:hover{color:var(--navy)}
.faq-list summary::after{
  content:"";flex:none;width:13px;height:13px;margin-top:.42em;margin-left:auto;
  background:var(--navy);transition:transform .25s var(--ease-in-view),background var(--t) var(--ease);
  clip-path:polygon(46% 0,54% 0,54% 46%,100% 46%,100% 54%,54% 54%,54% 100%,46% 100%,46% 54%,0 54%,0 46%,46% 46%);
}
.faq-list details[open] summary::after{transform:rotate(135deg);background:var(--amber)}
.faq-body{padding:0 3rem 1.7rem 0;max-width:64ch}
.faq-body p{color:var(--ink-soft);margin:0 0 .9rem}
.faq-body p:last-child{margin-bottom:0}
.faq-body a{color:var(--navy);text-decoration:underline;text-underline-offset:3px;text-decoration-color:var(--amber);text-decoration-thickness:2px}
/* Outbound citation. The claims about tax treatment and about what the public
   side provides are checkable, so they link to the authority that says so. */
.faq-body a[rel~="noopener"]::after{content:"\2197";font-size:.8em;margin-left:.15em;text-decoration:none;display:inline-block}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.faq-sources{margin-top:2.4rem;padding-top:1.3rem;border-top:1px solid var(--rule);font-size:var(--fs-micro);color:var(--ink-soft)}
.faq-sources a{color:var(--ink-soft);text-decoration:underline;text-underline-offset:2px}
.faq-list details[open] .faq-body{animation:faqOpen .34s var(--ease-in-view) both}
@keyframes faqOpen{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
.faq-foot{margin-top:2.2rem;display:flex;flex-wrap:wrap;gap:1rem 1.8rem;align-items:center}

/* cta band */
.cta-band{text-align:center}
.cta-band h2{margin:0 auto 1.9rem;max-width:20ch}
.cta-row{display:flex;gap:1rem 2rem;flex-wrap:wrap;justify-content:center;align-items:center}
.cta-row .tel{font-family:var(--serif);font-size:clamp(1.5rem,2.2vw,2rem)}

footer{background:var(--navy);color:var(--snow);padding:4rem 0 2.2rem;font-size:var(--fs-micro)}
.fgrid{display:grid;grid-template-columns:1.3fr 1fr 1fr 1fr;gap:2.5rem}
/* Footer group labels are h2, not h5. They were h5 under no h4, which Lighthouse
   flags as a skipped level. The visual treatment is unchanged. */
.fgrid h2{font-family:var(--sans);font-size:.78rem;font-weight:600;line-height:1.4;letter-spacing:.12em;text-transform:uppercase;margin:0 0 1rem;opacity:.65}
.fgrid ul{list-style:none;margin:0;padding:0}
.fgrid li{padding:.3rem 0}
.fgrid a{text-decoration:none;opacity:.9}
.fgrid a:hover{opacity:1;text-decoration:underline}
.fbot{margin-top:2.6rem;padding-top:1.5rem;border-top:1px solid rgba(246,241,231,.2);font-size:.88rem;opacity:.75;display:flex;gap:1.5rem;flex-wrap:wrap;justify-content:space-between}

/* ── entrance choreography ──
   Adopted from Hemby, the only site in the reference set that does it. Its
   technique is translateY fade-up with the DISTANCE varying by element mass
   (measured: fadeUpInfo 60px, fadeUpIcon 20px), which produces a natural
   stagger without hand-tuned per-element delays. --i adds a small index delay
   inside a group. Curve is Hemma's easeOutQuart.
   Scoped to .js, which an inline head script sets before first paint. Without
   it the hidden state never applies, so if the observer never runs the content
   is simply visible rather than permanently invisible. */
.js [data-rise]{
  opacity:0;transform:translateY(28px);
  transition:opacity var(--t-in-view) var(--ease-in-view),transform var(--t-in-view) var(--ease-in-view);
  transition-delay:calc(var(--i,0) * 70ms);
}
.js [data-rise="lg"]{transform:translateY(60px)}
.js [data-rise="sm"]{transform:translateY(20px)}
.js [data-rise].in{opacity:1;transform:none}

/* ── signature moment ──
   The amber arc draws itself on entrance. One motif, used exactly twice, on the
   hero and the closing CTA, and nowhere else. That restraint is Two Chairs'
   pattern ("one hand-drawn curve, used twice on the first two screens"); the
   drawing is ours. pathLength=1 normalises the dash maths across both sizes. */
.arc path{stroke-dasharray:1;stroke-dashoffset:1}
.arc.in path{animation:arcDraw 1.1s var(--ease-in-view) .28s forwards}
@keyframes arcDraw{to{stroke-dashoffset:0}}

/* ── hover craft ──
   Named properties only. Alma's `transition: all` on 312 elements is the lazy
   default, explicitly rejected in the benchmark. */
/* .scard is deliberately NOT in this list. It inverts on hover, which is the
   source's own interaction; adding a translateY on top would be two hover
   behaviours fighting each other. */
.svc,.mcol,.col{transition:transform var(--t-in-view) var(--ease-in-view),box-shadow var(--t-in-view) var(--ease-in-view)}
.svc:hover{transform:translateY(-4px);box-shadow:0 12px 34px -18px rgba(28,58,50,.42)}
.hero-media img,.why .slot img,.person .slot img{transition:transform .7s var(--ease-in-view)}
.hero-media:hover img,.why .slot:hover img,.person .slot:hover img{transform:scale(1.028)}
.editorial{transition:box-shadow var(--t-in-view) var(--ease-in-view)}
.editorial:hover{box-shadow:0 14px 40px -22px rgba(28,58,50,.4)}
.mark{transition:opacity var(--t) var(--ease)}
.mark:hover{opacity:.78}

/* demo markers */
/* No `white-space:nowrap` here. It was the root cause of 39-52px of horizontal
   overflow at 360: a long marker like "POSTIOSOITE: PRH:n osoite on kotiosoite"
   could not wrap, so it set the min-content width of its grid track and pushed
   the whole column past the viewport. */
/* Restyled onto the brand palette. These were amber, which was the LAST orange
   painting anywhere on the site once the accent moved to her logo blue; a demo
   marker is not a reason to keep a whole hue alive. scripts/check-palette.mjs
   walks the rendered DOM and fails if any warm hue reappears. */
.todo{background:rgba(12,72,132,.10);color:var(--navy-deep);border:1px dashed rgba(12,72,132,.55);padding:.14em .55em;border-radius:3px;font-size:.82em;font-weight:600;overflow-wrap:break-word}
/* The light-on-dark marker variant. `.hero` was in this list but the hero ground
   is cream, so the marker rendered #F3C67E on #F6F1E7 at 1.24:1, a WCAG failure
   caught by Lighthouse. A placeholder that cannot be read defeats its own purpose. */
.band.navy .todo,.testi .todo,footer .todo{background:rgba(245,248,252,.16);color:var(--amber);border-color:rgba(132,180,228,.55)}

@media (max-width:1000px){
  .hero .wrap,.person,.why,.testi,.matrix,.svc3{grid-template-columns:1fr}
  .why .slot{min-height:280px}
  .mcol{border-right:0;border-bottom:1px solid var(--rule)}
  .mcol:last-child{border-bottom:0}
  /* The nav is hidden at rest and revealed by the burger, which now has a
     handler (reveal.js). Before that it was hidden with no way to show it, so
     the site had no navigation below 1000px at all. */
  .nav{
    display:none;position:absolute;top:100%;left:0;right:0;
    background:#fff;border-bottom:1px solid var(--rule);
    flex-direction:column;gap:0;padding:.5rem var(--gut) 1.25rem;
    box-shadow:0 14px 28px -20px rgba(12,72,132,.35);
  }
  .nav a{padding:.85rem 0;border-bottom:1px solid var(--rule);font-size:1.05rem}
  .nav a:last-child{border-bottom:0}
  .nav-open .nav{display:flex}
  .hdr{position:sticky}
  .hdr .wrap{position:relative}
  .hdr-act{margin-left:auto}
  .burger{display:block;margin-left:0}
  .fgrid{grid-template-columns:1fr 1fr}
  .hero-media{aspect-ratio:16/10}
  .finder .pill{flex:1 1 auto}
  /* Was 4/3. A portrait forced into a landscape box is the reason her face was
     being cut off on phones. Stays portrait-shaped on every screen. */
  .person .slot{aspect-ratio:3/4;max-height:70vh}
}
/* 360px: the header cannot carry logo + phone + pill + burger. The brand rule
   says the phone survives at every breakpoint, so the pill is what goes. */
@media (max-width:620px){
  .hdr .wrap{gap:.75rem}
  .hdr-act .pill{display:none}
  .mark{font-size:1.25rem}
  .tel{font-size:.92rem}
  .fgrid{grid-template-columns:1fr}
  .cta-row{flex-direction:column;gap:1.1rem}
  /* Grid and flex children default to min-width:auto, so one unbreakable child
     can push a track wider than its container. This is what let the contact
     column overflow by 52px. */
  .split2>*,.cols3>*,.svc3>*{min-width:0}
}
/* At 360 the logo, the phone and the burger together still exceed the padded
   width. The phone is the one thing that must not go, so everything else gives. */
@media (max-width:400px){
  .hdr .wrap{gap:.5rem}
  .mark{font-size:1.1rem}
  .tel{font-size:.8rem}
  .tel svg{width:15px;height:15px}
  .burger{padding:.35rem}
}

/* ── header breakpoints ──
   Measured, not guessed. The full header row needs about 1200px:
     logo + wordmark ~200 · credential badge ~160 · nav (4 items) ~380
     · phone ~150 · CTA pill ~134 · gaps ~90 · gutters ~96
   The nav previously collapsed at 1000 and the badge hid at 900, so between
   1000 and 1200 everything was on screen at once and .hdr-act pushed 114px
   past the viewport. That band contains 1024, which is iPad landscape and a
   lot of small laptops, and NOTHING tested there: check-pages ran only 2560
   and 360. Widening that script to nine widths is what surfaced this.
   Two stages: drop the badge first, then fold the nav into the burger. */
@media (max-width:1200px){ .cred{display:none} }
@media (max-width:1100px){
  .nav{
    display:none;position:absolute;top:100%;left:0;right:0;
    background:#fff;border-bottom:1px solid var(--rule);
    flex-direction:column;gap:0;padding:.5rem var(--gut) 1.25rem;
    box-shadow:0 14px 28px -20px rgba(12,72,132,.35);
  }
  .nav a{padding:.85rem 0;border-bottom:1px solid var(--rule);font-size:1.05rem}
  .nav a:last-child{border-bottom:0}
  .nav-open .nav{display:flex}
  .hdr .wrap{position:relative}
  .hdr-act{margin-left:auto}
  .burger{display:block;margin-left:0}
}

@media (prefers-reduced-motion:reduce){
  *{animation:none!important;transition:none!important}
  html,.rail{scroll-behavior:auto}
  /* Killing transitions would otherwise strand every rise element at opacity 0
     and leave the arc undrawn. Both resolve to their finished state instead. */
  .js [data-rise]{opacity:1!important;transform:none!important}
  .arc path{stroke-dashoffset:0!important}
}

/* ── process rows ──
   REPLACES a three-across "01 02 03" module with thin rules on top, which I
   designed myself. It was not on the source, and a row of numbered columns with
   hairlines is the generic pattern that reads as machine-made. Copying the
   structure was the entire brief and I invented one instead.

   This is the source's actual construction, from its /how/ page
   (_capture/reference-slices/how/):
     - each step is a WIDE TWO-COLUMN ROW, not a card in a row of cards
     - photograph one side, text the other, and the sides ALTERNATE per step
     - the ground alternates with them: plain, then tinted
     - a large ITALIC SERIF numeral sits above the heading, on its own line
     - then a display heading, body copy at ~50ch, then a filled pill
     - rows are generous, about 700px tall at 1440
   Everything except the italic numeral already existed in our system, so this
   costs almost no new CSS; it just stops being the wrong shape. */
.howrow{display:grid;grid-template-columns:1fr 1fr;align-items:center;gap:clamp(2rem,5vw,5rem)}
/* 4/3 capped at 460, not the source's portrait 4/5. Measured with
   scripts/check-cta.mjs: at 4/5 the three rows totalled 3063px, 3.4 screens,
   the largest thing on a 10-screen page, and they pushed the services section
   to 5.9 screens deep. The source can afford portrait rows because they own a
   whole /how/ page; on a homepage this is one section among ten. Structure kept,
   bulk halved. */
.howrow-media{position:relative;aspect-ratio:4/3;max-height:460px;overflow:hidden;background:var(--ice)}
.howrow-media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease-in-view)}
.howrow-media:hover img{transform:scale(1.022)}
/* .flip puts the text first, which is what the source does on every second step */
.howrow.flip .howrow-media{order:2}
.howrow .n{
  display:block;font-family:var(--serif);font-style:italic;
  font-size:clamp(2.4rem,3.6vw,3.5rem);line-height:1;margin:0 0 1.3rem;color:var(--ink);
}
.howrow h3{margin-bottom:1.1rem}
.howrow p{color:var(--ink-soft);max-width:50ch;margin:0 0 1.9rem}
@media (max-width:900px){
  /* Image first on every row when stacked. Alternating order only reads as
     rhythm side by side; stacked it just looks inconsistent. */
  .howrow{grid-template-columns:1fr;gap:1.8rem}
  .howrow.flip .howrow-media{order:0}
  .howrow-media{aspect-ratio:4/3}
}

/* ── registers and permits ──
   The layer the clone source could never teach, because Home Instead is a UK
   franchise and the British equivalents are different institutions. Three of the
   four Finnish competitors publish an omavalvontasuunnitelma, three push
   kotitalousvähennys on the homepage, and Hemby badges VAT status right on the
   service picker. Every line rendered here is verified in ANCHOR §5 or in the
   raw PRH record; the pending ones are simply absent, not stubbed. */
.regs{display:grid;grid-template-columns:.85fr 1.15fr;gap:clamp(2rem,5vw,5rem);align-items:start}
.reglist{margin:0;border-top:1px solid var(--rule)}
.reglist dt{font-weight:600;padding-top:1.2rem;display:flex;gap:.75rem;align-items:baseline}
.reglist dt::before{content:"";width:8px;height:8px;background:var(--amber);flex:none;transform:translateY(-2px)}
.reglist dd{margin:.35rem 0 0 1.55rem;padding-bottom:1.2rem;color:var(--ink-soft);border-bottom:1px solid var(--rule);font-size:var(--fs-small)}
.reglist dd a{color:var(--navy);text-decoration:underline;text-underline-offset:3px;text-decoration-color:var(--amber);text-decoration-thickness:2px}

/* VAT badge. Hemby's mechanic: the service picker carries `ALV 0 %` against
   `ALV 25,5 %` so the buyer sees which rate applies before the price. Ours is
   an explainer rather than an assertion, because which rate applies to Janica
   is an open commercial question, not a settled fact. */
.alv{display:inline-flex;align-items:center;gap:.45rem;padding:.28rem .7rem;border-radius:999px;font-size:.82rem;font-weight:600;letter-spacing:.01em;background:var(--navy-10);color:var(--navy);border:1px solid rgba(28,58,50,.2);white-space:nowrap}
.mcol.lead .alv{background:rgba(246,241,231,.16);color:var(--snow);border-color:rgba(246,241,231,.34)}
.alvnote{margin:1.8rem 0 0;padding:1.3rem 1.5rem;border:1px solid var(--rule);border-radius:8px;background:#fff;font-size:var(--fs-small);color:var(--ink-soft);max-width:78ch}
.alvnote strong{color:var(--ink)}
.alvnote a{color:var(--navy);text-decoration:underline;text-underline-offset:3px;text-decoration-color:var(--amber);text-decoration-thickness:2px}

@media (max-width:1000px){
  .steps{grid-template-columns:1fr;gap:1.8rem}
  .regs{grid-template-columns:1fr;gap:1.8rem}
}

/* ── subpage primitives ── */
.banner{background:var(--snow);padding:clamp(3rem,5vw,5.5rem) 0 clamp(2.5rem,4vw,4.5rem)}
.banner h1{max-width:17ch}
.banner .lede{margin-top:1.5rem}
.crumb{font-size:.78rem;letter-spacing:.12em;text-transform:uppercase;color:var(--amber);font-weight:600;margin:0 0 1.1rem}
.prose{max-width:66ch}
.prose p{color:var(--ink-soft);margin:0 0 1.15rem}
.prose h3{margin:2.4rem 0 .8rem;color:var(--ink)}
/* The legal pages had their own sections marked h3 directly under the h1, so
   heading navigation skipped a level. Corrected to h2 for structure; these two
   rules keep the RENDERED size identical to what the h3s resolved to, because
   the global h2 is much larger and a naive tag swap would have redesigned the
   page. Verified by comparing computed styles before and after. */
.prose h2{font-size:clamp(1.75rem,3.34vw,3rem);letter-spacing:normal;margin:2.4rem 0 .8rem;color:var(--ink)}
.col h2.colh{font-size:1.55rem;letter-spacing:normal;margin:0 0 .7rem}
.cols3{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(1.5rem,3vw,3rem)}
.col h3{font-size:1.55rem;margin-bottom:.7rem}
.col p{margin:0;color:var(--ink-soft)}
.col .rule{width:44px;height:2px;background:var(--amber);margin-bottom:1.3rem}
/* Package ladder. A real table, because it is tabular data and a screen reader
   should be able to say "TURVA, 8 käyntiä, 1200 euroa" as one row. */
.plist{width:100%;max-width:28rem;border-collapse:collapse;margin:0}
/* Utan tak spreds tre korta kolumner over 900px och lamnade ~600px tomt
   mellan besoksantal och pris, sa ogat tappade raden pa vagen. */
.plist caption{text-align:left;font-family:var(--serif);font-size:1.5rem;margin-bottom:1rem;color:var(--ink)}
.plist th,.plist td{text-align:left;padding:.85rem 0;border-bottom:1px solid var(--rule);vertical-align:baseline}
.plist thead th{font-size:var(--fs-micro);text-transform:uppercase;letter-spacing:.1em;color:var(--ink-soft);font-weight:600;padding-top:0}
.plist tbody th{font-weight:600;letter-spacing:.04em}
/* Scoped to tbody. Unscoped, `td:last-child` also matched the footer cell, whose
   colspan spans the whole table: a full sentence forbidden from wrapping set the
   table's min-content width to 595px and pushed 255px past a 360 viewport. Same
   bug class as the `.todo` nowrap this project shipped once before. */
.plist tbody td:last-child{text-align:right;font-family:var(--serif);font-size:1.25rem;white-space:nowrap}
/* HINTA-rubriken var vanstersstalld medan varden ar hogerstallda, sa
   rubrik och siffror stod inte i samma kolumn. */
.plist thead th:last-child{text-align:right}
.plist tbody td:nth-child(2){color:var(--ink-soft);font-size:var(--fs-small)}
/* 'tehostettu jakso' tvingade mittkolumnen bred och lamnade ~390px tomt
   mellan besoksantal och pris. Fast bredd + wrap drar ihop raden. */
.plist th:nth-child(2),.plist tbody td:nth-child(2){width:8rem;white-space:normal}
.plist tfoot td{border-bottom:0;padding-top:1rem;color:var(--ink-soft);font-size:var(--fs-micro);text-align:left;white-space:normal;font-family:var(--sans)}
@media (max-width:520px){
  .plist td:last-child{font-size:1.05rem}
  .plist thead{display:none}
}

dl.terms{margin:0;border-top:1px solid var(--rule)}
dl.terms dt{font-weight:600;padding-top:1.05rem}
dl.terms dd{margin:.2rem 0 1.05rem;padding-bottom:1.05rem;color:var(--ink-soft);border-bottom:1px solid var(--rule)}
.split2{display:grid;grid-template-columns:1fr 1fr;gap:clamp(2rem,4vw,5rem);align-items:start}
.field{display:block;margin-bottom:1.15rem}
.field span{display:block;font-weight:600;font-size:var(--fs-micro);margin-bottom:.4rem}
.field input,.field textarea,.field select{width:100%;font:inherit;padding:.85rem 1.1rem;border:1.5px solid var(--rule);border-radius:8px;background:#fff;color:var(--ink)}
.field textarea{min-height:8rem;resize:vertical}
.field input:focus,.field textarea:focus,.field select:focus{outline:none;border-color:var(--amber)}
.consent{display:flex;gap:.7rem;align-items:flex-start;font-size:var(--fs-micro);color:var(--ink-soft);margin:1.2rem 0 1.6rem}
.consent input{margin-top:.3rem;flex:none}
/* Min lowered from 3rem: at 360 a 13-character placeholder set at 48px is wider
   than the screen and drags its grid track with it. */
.bignum{font-family:var(--serif);font-size:clamp(2.2rem,5.5vw,5rem);line-height:1.12;letter-spacing:-.03em;margin:1.8rem 0 0;overflow-wrap:break-word}
/* The struck base price on the free ensikaynti. Small and above the 0 so the
   free number stays the one you read first. */
.bignum .was{display:block;font-family:var(--sans);font-size:.3em;font-weight:400;letter-spacing:0;color:var(--ink-soft);text-decoration-thickness:.08em;margin-bottom:.3em}
.bignum sup{font-size:.26em;vertical-align:baseline;margin-left:.35em;color:var(--ink-soft);letter-spacing:0;position:static}
@media (max-width:1000px){
  .cols3,.split2,.faq{grid-template-columns:1fr}
  .faq{gap:1.8rem}
  .faq-body{padding-right:1rem}
  /* Stacked, the aside's CTA row repeats the CTA band immediately above it and
     pushes every question below the fold. The questions are the point here. */
  .faq-foot{display:none}
  .faq-aside p{margin-bottom:0}
}
/* ---------------------------------------------------------------------------
   Request form (yhteystiedot.html #lomake).
   Labels sit ABOVE their inputs, never as placeholders: placeholder-as-label
   disappears on focus and fails WCAG 3.3.2, and this page is read by people who
   are already tired. Inputs are 48px tall so the touch target clears 2.5.8, and
   the focus ring is the shared :focus-visible amber rather than a local one.
   --------------------------------------------------------------------------- */
.cform{margin-top:2rem;max-width:34rem}
.fld{margin-bottom:1.4rem}
.fld label{display:block;font-weight:600;font-size:var(--fs-small);margin-bottom:.45rem;color:var(--ink)}
.fld input,.fld textarea{
  width:100%;min-height:48px;font:inherit;font-size:1rem;color:var(--ink);
  background:#fff;border:1.5px solid rgba(28,58,50,.28);border-radius:10px;
  padding:.75rem 1rem;transition:border-color .15s cubic-bezier(0,0,.2,1);
}
.fld textarea{min-height:8.5rem;resize:vertical;line-height:1.55}
.fld input:focus,.fld textarea:focus{outline:none;border-color:var(--navy);box-shadow:0 0 0 3px var(--navy-10)}
.fld input:focus-visible,.fld textarea:focus-visible{outline:3px solid var(--amber);outline-offset:2px}
.hint{margin:.45rem 0 0;font-size:var(--fs-micro);color:var(--ink-soft)}

.chk{display:flex;align-items:flex-start;gap:.7rem;margin-bottom:1rem}
/* 20px box with its own margin rather than a scaled 13px default: transform
   scaling a checkbox leaves the hit area at the original size. */
.chk input{flex:none;width:20px;height:20px;margin:.15rem 0 0;accent-color:var(--navy)}
.chk label{font-size:var(--fs-micro);line-height:1.5;color:var(--ink-soft)}
.chk a{color:var(--navy)}

.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

.cform button[type=submit]{margin-top:.9rem}
.cform button[disabled]{opacity:.6;cursor:progress}
.fnote{margin:1rem 0 0;font-size:var(--fs-micro);color:var(--ink-soft);max-width:40ch}

/* Result panels. Left border rather than a filled card so neither state shouts;
   the error one carries the phone as a real pill, because at that moment the
   phone IS the fallback and a text link would read as an apology. */
.fmsg{margin-top:2rem;border-left:3px solid var(--navy);background:var(--snow);padding:1.6rem 1.8rem;border-radius:0 10px 10px 0;max-width:34rem}
.fmsg.bad{border-left-color:#B4462F;background:#FCF3F1}
.fmsg h3{margin:0 0 .6rem;font-size:1.25rem}
.fmsg p{margin:0;color:var(--ink-soft);font-size:var(--fs-small);line-height:1.6}
.fmsg a{color:var(--navy)}
.fdirect{display:flex;align-items:center;gap:1.1rem;flex-wrap:wrap;margin-top:1.2rem!important}

/* Struck reference price. Muted and lighter than the live figure so the eye
   lands on what the reader actually pays, and marked up as <s> so a screen
   reader announces it as no longer applicable rather than reading two prices
   as a range. */
.hero-offer s,.bignum s{color:var(--ink-soft);opacity:.65;font-weight:400;text-decoration-thickness:1.5px}
.bignum s:not(.was){margin-right:.35em;font-size:.5em;vertical-align:.35em}

/* The closing band's form. The band is centre-aligned, so the form gets its own
   left-aligned column: centred labels over left-aligned inputs read as a mistake,
   and a label must sit at the same edge as the field it names. */
.cta-form{max-width:34rem;margin:2.4rem auto 0;text-align:left}
.cta-form .cform{margin-top:0;max-width:none}
.cta-form .fmsg{margin-left:auto;margin-right:auto}

/* Andra prisrubriken pa hinnasto: lika mycket luft som sektionsavstand. */
.price-h2{margin-top:3.6rem}

/* ===== 2026-10-09: group pages (Läheiselle / Ikääntyneelle / Kuntoutujalle /
   Kotiutujalle) and the hinnasto package cards. Reuses .scard's look in a
   wrapping grid instead of the homepage's scroll rail; these cards are content,
   not links, so they do not invert on hover. */
.cardgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;align-items:stretch}
.cardgrid .scard h3{font-size:clamp(1.6rem,2.2vw,2rem);line-height:1.1}
@media (max-width:1000px){ .cardgrid{grid-template-columns:repeat(2,1fr)} }
@media (max-width:640px){ .cardgrid{grid-template-columns:1fr} }
.cardgrid .scard:hover{background:var(--ice);color:inherit}
.cardgrid .scard:hover .icon{color:var(--navy)}
.cardgrid .scard li{padding:.35rem 0;border-top:1px solid var(--rule)}
.cardgrid .scard li:first-child{border-top:0;padding-top:0}
.pkg{gap:14px}
.pkg h3{font-family:var(--serif);font-size:2.2rem;font-weight:400;line-height:1}
.pkg-sub{margin:0;font-weight:600;color:var(--navy)}
.pkg-price{margin:0;font-size:1.35rem;font-weight:700;color:var(--ink)}
.pkg p{margin:0}
.pkg>p:not(.pkg-sub):not(.pkg-price):not(.pkg-lh){color:var(--ink-soft);font-size:var(--fs-small)}
.pkg-lh{font-weight:600;font-size:var(--fs-small)}
.nolist{list-style:none;margin:0 0 1.6rem;padding:0}
.nolist li{color:var(--ink-soft);padding:.8rem 0 .8rem 1.4rem;border-top:1px solid var(--rule);position:relative}
.nolist li:last-child{border-bottom:1px solid var(--rule)}
.nolist li::before{content:"";position:absolute;left:0;top:1.35rem;width:8px;height:2px;background:var(--amber)}
.cols3.two{grid-template-columns:repeat(2,1fr)}
.subh{font-size:1.55rem;margin:2.6rem 0 1.4rem}
.callout{background:var(--ice);border-left:4px solid var(--navy);border-radius:var(--card-radius);padding:clamp(1.4rem,3vw,2.2rem);margin:2rem 0 0;max-width:62ch;font-family:var(--serif);font-size:clamp(1.35rem,2.2vw,1.75rem);line-height:1.35;color:var(--ink)}
.callout p{margin:0}
@media (max-width:760px){ .cols3.two{grid-template-columns:1fr} }

/* 2026-10-09 visual QA fixes.
   1. Kenelle cards: .svc3.four out-specified the 1000px .svc3 collapse, so phones
      got two ~150px columns and long words broke out of the card. */
.svc3.four .svc{min-height:230px}
@media (max-width:760px){ .svc3.four{grid-template-columns:1fr} .svc3.four .svc{min-height:0} }
/* 2. "Miksi yhdessä" card: her text is ~2x the old length. Wider text column on
      desktop; stacked with the photo on top on phones instead of a 40px strip. */
.ecard{max-width:1080px;grid-template-columns:3fr 2fr}
@media (max-width:760px){
  .ecard{grid-template-columns:1fr;min-height:0}
  .ecard-media{min-height:240px;order:-1}
}
/* 3. Footer CTA was navy on the navy footer, i.e. invisible. */
footer .pill{background:var(--amber);border-color:var(--amber);color:var(--amber-ink)}
footer .pill:hover{background:var(--amber-lift);border-color:var(--amber-lift)}
/* 4. Kotiutujalle / Läheiselle have one list, not a set of cards: full width,
      two text columns on desktop instead of one card stranded at 1/3 width. */
.cardgrid.single{grid-template-columns:1fr}
.cardgrid.single .scard ul{columns:2;column-gap:3rem}
.cardgrid.single .scard li{break-inside:avoid;border-top:0;border-bottom:1px solid var(--rule);padding:.6rem 0}
@media (max-width:760px){ .cardgrid.single .scard ul{columns:1} }
