/* ============================================================================
   Woods Helsinki — design system
   Built from design-synthesis.md (token table, type system, rooms, motion
   constants) + docs/fonts-faces.css. Concept: UPOTUS — light content set flush
   into dark ground; varjosauma (shadow gap) is the ONE seam treatment.
   Nothing here is derived from the briefs; the synthesis wins every conflict.
   ============================================================================ */

/* ---------------------------------------------------------------- fonts ---
   Verbatim from docs/fonts-faces.css. The Literata ITALIC faces are declared
   there too but are deliberately not shipped here: italic is reserved for real
   client quotes (design-synthesis "Testimonial slots … render ONLY with real
   client quotes"), none exist yet, and an unused face is 70.9 kB against the
   600 kB page gate. Add both italic blocks back when the quotes land. */

@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('fonts/jost-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: 'Jost';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('fonts/jost-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: 'Jost';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/jost-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: 'Jost';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/jost-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: 'Jost';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/jost-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: 'Jost';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/jost-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: 'Literata';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/literata-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: 'Literata';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/literata-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: 'Literata';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/literata-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: 'Literata';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/literata-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;
}

/* ---------------------------------------------------------------- tokens --
   Palette 14.10.2026: spruce and moss in place of beige and walnut (owner).
   The variable names are the old ones; the values are not. */
:root {
  --musta-0: oklch(0.17 0.014 165);
  --musta-1: oklch(0.21 0.02 163);
  --pahkina: oklch(0.31 0.05 158);
  --kalkki: oklch(0.915 0.012 135);
  --kalkki-syva: oklch(0.86 0.02 140);
  --paperi: oklch(0.975 0.004 120);
  --saarni: oklch(0.92 0.03 130);
  --tammi: oklch(0.80 0.075 128);
  --liuske: oklch(0.24 0.035 162);
  --kuusi-syva: oklch(0.25 0.045 158);   /* filled button hover on light grounds */

  --font-ui: 'Jost', ui-sans-serif, system-ui, sans-serif;
  --font-reading: 'Literata', Georgia, 'Times New Roman', serif;

  /* varjosauma — the recessed musta-0 reveal that joins same-lightness rooms,
     articulates panels and draws the focus/current notch. One seam, one scale. */
  --sauma: 8px;
  --sauma-syva: 10px;

  --mitta: 1240px;                       /* content measure */
  --reuna: clamp(20px, 5vw, 72px);       /* page gutter */
  --huone-pysty: clamp(72px, 12vw, 176px);/* room vertical rhythm — raised 3.9. from
                                            clamp(64px, 9vw, 132px): the owner read the
                                            page as crammed; nothing tall ever happened */
  --ylitys: 0px;                         /* waterfall crossing — desktop only: the
                                            band is stacked below 1024px, where there is
                                            no side column to hang from */
  --palkki: 60px;                        /* header height */

  /* motion constants — design-synthesis §Motion, copied verbatim */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --nousu: 12px;
  --kesto: 500ms;
  --mikro: 120ms;
  --paino: 80ms;
}

@media (min-width: 1024px) {
  :root { --palkki: 76px; --ylitys: 64px; }
}

/* ---------------------------------------------------------------- reset ---- */
*, *::before, *::after { box-sizing: border-box; }

html {
  font-family: var(--font-reading);
  font-size: 17px;
  line-height: 1.6;
  font-synthesis: none;
  -webkit-text-size-adjust: 100%;
  hyphens: auto;
  background: var(--musta-0);
  /* the header is fixed at 60/76px: without this, keyboard focus and #sisalto
     skips park their target underneath the bar */
  scroll-padding-top: calc(var(--palkki) + 24px);
}

body {
  margin: 0;
  color: var(--paperi);
  background: var(--musta-0);
  overflow-wrap: break-word;
}

img, picture, svg { display: block; max-width: 100%; }
img { height: auto; }

h1, h2, h3, p, ul, figure { margin: 0; }
ul { padding: 0; list-style: none; }

a { color: inherit; }

::selection { background: var(--tammi); color: var(--musta-0); }

/* ---------------------------------------------------------------- rooms ----
   A room owns its ground and the ink that reads on it. Everything downstream
   reads --muste / --hiljainen / --linkki, so a component never hardcodes a
   ground and can be moved between rooms without a contrast surprise. */
.room {
  position: relative;
  background: var(--ground);
  color: var(--muste);
}

.room--musta {
  --ground: var(--musta-0);
  --muste: var(--paperi);
  --hiljainen: color-mix(in oklab, var(--paperi) 76%, var(--musta-0));
  --linkki: var(--tammi);
  --viiva: color-mix(in oklab, var(--paperi) 22%, var(--musta-0));
}
.room--musta-1 {
  --ground: var(--musta-1);
  --muste: var(--paperi);
  --hiljainen: color-mix(in oklab, var(--paperi) 76%, var(--musta-1));
  --linkki: var(--tammi);
  --viiva: color-mix(in oklab, var(--paperi) 24%, var(--musta-1));
}
.room--pahkina {
  --ground: var(--pahkina);
  --muste: var(--paperi);
  --hiljainen: color-mix(in oklab, var(--paperi) 80%, var(--pahkina));
  --linkki: var(--saarni);
  --viiva: color-mix(in oklab, var(--paperi) 30%, var(--pahkina));
}
.room--liuske {
  --ground: var(--liuske);
  --muste: var(--paperi);
  --hiljainen: color-mix(in oklab, var(--paperi) 74%, var(--liuske));
  --linkki: var(--tammi);
  --viiva: color-mix(in oklab, var(--paperi) 20%, var(--liuske));
}
.room--kalkki {
  --ground: var(--kalkki);
  --muste: var(--musta-0);
  --hiljainen: color-mix(in oklab, var(--musta-0) 78%, var(--kalkki));
  --linkki: var(--musta-0);
  --viiva: color-mix(in oklab, var(--musta-0) 26%, var(--kalkki));
}
.room--kalkki-syva {
  --ground: var(--kalkki-syva);
  --muste: var(--musta-0);
  --hiljainen: color-mix(in oklab, var(--musta-0) 78%, var(--kalkki-syva));
  --linkki: var(--musta-0);
  --viiva: color-mix(in oklab, var(--musta-0) 26%, var(--kalkki-syva));
}
.room--paperi {
  --ground: var(--paperi);
  --muste: var(--musta-0);
  --hiljainen: color-mix(in oklab, var(--musta-0) 78%, var(--paperi));
  --linkki: var(--musta-0);
  --viiva: color-mix(in oklab, var(--musta-0) 24%, var(--paperi));
}

/* varjosauma between two rooms of the same lightness family: a recessed
   musta-0 reveal, lit from above like a real shadow gap. Deep-contrast joints
   need no seam — there the surfaces are the edge (synthesis, graft 1). */
.room--sauma::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--sauma);
  background: var(--musta-0);
  box-shadow: inset 0 3px 5px -1px rgba(0, 0, 0, 0.75);
  z-index: 3;
}

.sisus {
  max-width: var(--mitta);
  margin-inline: auto;
  padding-inline: var(--reuna);
}

.huone-pysty { padding-block: var(--huone-pysty); }

/* an intro block that hands off to a full-bleed run below it */
.johdanto {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(18px, 2.2vw, 32px);
  padding-block: var(--huone-pysty) clamp(40px, 5vw, 80px);
}

/* the room that receives a waterfall joint starts below the overhang (top only —
   it composes with .johdanto, which owns the bottom) */
.huone-ylitys { padding-top: calc(var(--huone-pysty) + var(--ylitys)); }

/* ---------------------------------------------------------------- type ----- */
.kicker {
  font-family: var(--font-ui);
  /* 500 + tighter tracking: at 400/0.18em the strokes render ~1px and break up
     under a Meet-grade encoder (Tier-2 screenshare read, 6.8.) */
  font-weight: 500;
  font-size: 13px;
  line-height: 1.3;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--hiljainen);
}

h1, .h1 {
  font-family: var(--font-ui);
  font-weight: 300;
  font-size: clamp(2.25rem, 5.4vw, 4rem);
  line-height: 1.05;
  letter-spacing: -0.012em;
  text-wrap: balance;
}

h2, .h2 {
  font-family: var(--font-ui);
  font-weight: 300;
  font-size: clamp(1.625rem, 3.1vw, 2.125rem);
  line-height: 1.12;
  letter-spacing: -0.008em;
  text-wrap: balance;
}

/* poster caps — short section-opener words only (≤12 chars), never an H1 */
.juliste {
  font-family: var(--font-ui);
  font-weight: 300;
  font-size: clamp(4rem, 9vw, 7.5rem);
  line-height: 0.92;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

.lead {
  font-family: var(--font-reading);
  font-size: clamp(1.0625rem, 1.1vw + 0.7rem, 1.375rem);
  line-height: 1.5;
  max-width: 32ch;
  color: var(--muste);
}

.teksti {
  font-family: var(--font-reading);
  font-size: 1rem;
  line-height: 1.6;
  max-width: 58ch;
  color: var(--hiljainen);
}

/* product / spec lines: Jost caps with dot grammar (KLIFFA · SAARNI, TAMMI) */
.spec {
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 0.8125rem;
  line-height: 1.35;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muste);
}

.merkinta {
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 0.875rem;
  letter-spacing: 0.06em;
  color: var(--hiljainen);
}

/* ---------------------------------------------------------------- CTA ------
   The inlay: a filled block of the opposite material set flush into the
   ground — the client's own CNC-inlaid wordmark, at interaction scale. */
.cta {
  --cta-y: 0px;
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  min-height: 52px;
  padding: 0 1.5rem;
  font-family: var(--font-ui);
  font-weight: 500;
  font-size: 0.875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  border: 0;
  transform: translateY(var(--cta-y));
  transition:
    background-color var(--mikro) var(--ease),
    color var(--mikro) var(--ease),
    border-color var(--mikro) var(--ease),
    transform var(--paino) var(--ease);
}

.cta--inlay {
  background: var(--saarni);
  color: var(--musta-0);
}
.cta--inlay:hover { background: var(--paperi); }
.cta--inlay:active { --cta-y: 1px; }

/* the inlay on a light room is the OPPOSITE material: the dark block. One
   mechanism, both grounds — not a second button. Lives in the shared sheet
   because index.html (which never loads sivut.css) now opens and closes light. */
.room--kalkki .cta--inlay,
.room--kalkki-syva .cta--inlay,
.room--paperi .cta--inlay {
  background: var(--pahkina);
  color: var(--paperi);
}
.room--kalkki .cta--inlay:hover,
.room--kalkki-syva .cta--inlay:hover,
.room--paperi .cta--inlay:hover { background: var(--kuusi-syva); }

/* secondary: no fill; the arrow carries the affordance and a hairline sits
   under the words themselves, not under the tap target's box */
.cta--linkki {
  position: relative;
  padding: 0 0.25rem;
  color: var(--linkki);
  background: transparent;
}
.cta--linkki::after {
  content: '';
  position: absolute;
  left: 0.25rem;
  right: calc(0.25rem + 22px + 0.6em);
  bottom: calc(50% - 0.95em);
  /* 2px in the link tone: a 1px --viiva rule measured ~1.8:1 through a
     Meet-grade encoder and vanished, taking the link affordance with it
     (Tier-2 screenshare read, 6.8.) */
  height: 2px;
  background: color-mix(in oklab, var(--linkki) 72%, transparent);
  transition: background-color var(--mikro) var(--ease);
}
.cta--linkki:hover::after { background: var(--linkki); }
.cta--linkki:active { --cta-y: 1px; }

.cta-rivi {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 20px;
  align-items: center;
}

/* arrow — Jost has no U+2192 (docs/fonts-faces.css). One mechanism sitewide:
   an inline SVG chevron, which also gives the 120ms/4px slide something real. */
.arrow {
  flex: none;
  width: 22px;
  height: 10px;
  overflow: visible;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: square;
  transition: transform var(--mikro) var(--ease);
}
.cta:hover .arrow, .cta:focus-visible .arrow { transform: translateX(4px); }

/* ---------------------------------------------------------------- focus ----
   The focus ring is the tammi notch. On light grounds tammi alone is only
   ~2.2:1 against kalkki, so it is doubled with a musta-0 core there — the
   notch stays the same object, it just carries its own shadow gap. */
:focus-visible {
  outline: 2px solid var(--tammi);
  outline-offset: 3px;
}
.room--kalkki :focus-visible,
.room--kalkki-syva :focus-visible,
.room--paperi :focus-visible,
.paalkki :focus-visible {
  outline-color: var(--musta-0);
  box-shadow: 0 0 0 5px var(--tammi);
}

/* ---------------------------------------------------------------- header ---
   The header is a paperi room (3.9.: the client read the dark shell as "way too
   dark and gloomy"; every page opened under a musta bar). It carries the same
   --ground/--muste tokens as any room, so nothing inside it names a colour: the
   nav, the burger, the mobile panel and the notch all read the room. The
   current-page notch is pähkinä here, not tammi — the token table binds tammi
   to dark grounds only (≈2.2:1 on kalkki). */
.paalkki {
  --ground: var(--paperi);
  --muste: var(--musta-0);
  --viiva: color-mix(in oklab, var(--musta-0) 14%, var(--paperi));
  --lovi: var(--pahkina);
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 40;
  background: var(--ground);
  color: var(--muste);
  border-bottom: 1px solid var(--viiva);
  transition: background-color var(--mikro) var(--ease), border-color var(--mikro) var(--ease);
}

/* Transparent over the hero only while JS is there to put it back — the
   inverted gate applied to chrome: without JS the header is solid and legible
   over every room it can ever sit above. */
html:not(.no-js):not(.scrolled) .paalkki {
  background: transparent;
  border-bottom-color: transparent;
}

.paalkki__sisus {
  display: flex;
  align-items: center;
  gap: clamp(8px, 2vw, 28px);
  max-width: var(--mitta);
  margin-inline: auto;
  padding-inline: clamp(12px, 5vw, 72px);
  min-height: var(--palkki);
}

.paalkki__merkki {
  display: flex;
  align-items: center;
  min-height: 44px;
  margin-right: auto;
}
.paalkki__merkki img {
  width: clamp(104px, 24vw, 190px);
  height: auto;
}

.paavalikko__lista {
  display: flex;
  /* Capped at 24px, not 30px, and off a 1.4vw slope: `.paalkki__sisus` is capped
     at `--mitta` (1240px) but this gap tracked the VIEWPORT, so above ~1600px the
     gaps kept growing inside a container that could not, and MUUT TILAT wrapped to
     two lines (measured 64px vs 42px item height; chromium ≥1920, webkit ≥1600 —
     the reviewer's own monitor). Verified single-line at 1024–3440 in both engines. */
  gap: clamp(14px, 1.4vw, 24px);
  margin: 0;
}
.paavalikko a {
  display: block;
  position: relative;
  padding: 10px 0;
  white-space: nowrap;
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 0.8125rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  text-decoration: none;
  color: color-mix(in oklab, var(--muste) 82%, var(--ground));
  transition: color var(--mikro) var(--ease);
}
.paavalikko a:hover { color: var(--muste); }

/* current-page notch: the varjosauma at label scale */
.paavalikko a[aria-current='page'] { color: var(--muste); }
.paavalikko a[aria-current='page']::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2px;
  height: 3px;
  background: var(--lovi);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35);
}

/* the header's inlay is the dark block: opposite material of a paperi ground */
.paalkki .cta--inlay { background: var(--pahkina); color: var(--paperi); }
.paalkki .cta--inlay:hover { background: var(--kuusi-syva); }

.paalkki__yhteys {
  flex: none;
  min-height: 44px;
  padding: 0 clamp(0.7rem, 2vw, 1.25rem);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  white-space: nowrap;
}

.paalkki__valikko {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--viiva);
  color: var(--muste);
  cursor: pointer;
  transition: background-color var(--mikro) var(--ease), border-color var(--mikro) var(--ease);
}
.paalkki__valikko:hover { background: color-mix(in oklab, var(--muste) 8%, transparent); }
.paalkki__viivat, .paalkki__viivat::before, .paalkki__viivat::after {
  display: block;
  width: 18px;
  height: 1px;
  background: currentColor;
}
.paalkki__viivat { position: relative; }
.paalkki__viivat::before, .paalkki__viivat::after {
  content: '';
  position: absolute;
  left: 0;
}
.paalkki__viivat::before { top: -6px; }
.paalkki__viivat::after { top: 6px; }
[aria-expanded='true'] .paalkki__viivat { background: transparent; }
[aria-expanded='true'] .paalkki__viivat::before { top: 0; transform: rotate(45deg); }
[aria-expanded='true'] .paalkki__viivat::after { top: 0; transform: rotate(-45deg); }

/* mobile: the panel drops from the header's trailing edge */
@media (max-width: 1023px) {
  .paavalikko {
    display: none;
    position: absolute;
    top: 100%;
    right: 0;
    left: 0;
    background: var(--ground);
    border-bottom: 1px solid var(--viiva);
    padding: 4px clamp(12px, 5vw, 72px) 18px;
  }
  .paavalikko[data-auki] { display: block; }
  .paavalikko__lista { flex-direction: column; gap: 0; }
  .paavalikko a {
    padding: 13px 0;
    font-size: 0.9375rem;
    border-bottom: 1px solid var(--viiva);
  }
  .paavalikko a[aria-current='page']::after { right: auto; width: 26px; bottom: 8px; }
}

@media (min-width: 1024px) {
  .paalkki__valikko { display: none; }
  .paavalikko { display: block; }
}

/* ---------------------------------------------------------------- hero -----
   Cabinetry elevation: poster room over a full-width photo band, the two
   flush to the fold. The poster room is paperi since 3.9. (client: too dark);
   the grammar is the room's, not the colour's. Phone keeps the grammar stacked; the type clamps first so
   the band never drops under its 220px floor. */
.hero {
  display: grid;
  grid-template-rows: 1fr auto;
  min-height: 100vh;
  min-height: 100svh;
}

/* Everything in the poster room is viewport-HEIGHT aware: the fold is the
   constraint, so the type clamps against svh as well as vw.

   The three rhythm tokens are svh-clamped with an OFFSET, not a plain ratio: a
   plain `1.4svh` shrinks too slowly to buy the fold anything on a short screen,
   because the type has already hit its own floors there. Subtracting a constant
   makes the rhythm yield first and steeply as the viewport shortens, while the
   tall cells keep (or slightly exceed) the spacing they had. Floors are the
   sane minimum: 4px still reads as a joint, 2px still separates the CTA row.
   This is what lets DOD row 13's injected lead (the Keittiöt sentence, copy-deck
   line 149) compress the rhythm instead of pushing the photo band past the fold
   at 320×568 and 1280×640. */
.hero__juliste {
  --rytmi: clamp(4px, calc(3.1svh - 13.6px), 22px);
  --rytmi-reuna: clamp(4px, calc(4.6svh - 22px), 40px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--rytmi);
  padding-top: calc(var(--palkki) + var(--rytmi-reuna));
  padding-bottom: var(--rytmi-reuna);
  max-width: var(--mitta);
  width: 100%;
  margin-inline: auto;
  padding-inline: var(--reuna);
}

.hero h1 {
  font-size: min(clamp(2.25rem, 5.4vw, 4rem), 7svh);
  max-width: 26ch;
}
.hero .lead {
  /* the lead clamps against the fold, but never below body size — a lead
     smaller than the paragraphs it introduces is a broken hierarchy */
  font-size: max(1.0625rem, min(clamp(1.0625rem, 0.5vw + 0.75rem, 1.1875rem), 2.75svh));
  max-width: 64ch;
  color: color-mix(in oklab, var(--muste) 84%, var(--ground));
}
.hero .cta-rivi { margin-top: clamp(2px, calc(2.6svh - 12px), 16px); }

/* Two-column poster (≥1024px) — the synthesis binds brief B's hero: the
   supporting sentence sits in the poster's right 5/12, not stacked under the
   H1. Built as a single column, it left the right ~45% of the fold an empty
   dark field; three independent cold reads (5-second, screenshare-robustness,
   shared-grammar adversary — 6.8.) named that void, and the adversary noted it
   is exactly the slot a sibling client's site fills with an image card, which
   is what made the two read as one container order. The lead moving right
   fills the width AND shortens the stack, which buys the photo band height. */
@media (min-width: 1024px) {
  /* DOM order stays semantic (kicker → H1 → lead → CTA) so the phone reads
     what-we-do BEFORE it is asked for a quote; only the desktop grid moves the
     lead into the poster's right 5/12. Placing by grid rather than by wrapper
     is what keeps those two facts independent. */
  .hero__juliste {
    display: grid;
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    grid-template-rows: auto auto auto;
    column-gap: clamp(2rem, 4.5vw, 5rem);
    align-content: center;
  }
  .hero__juliste > .kicker { grid-column: 1; grid-row: 1; }
  .hero__juliste > h1      { grid-column: 1; grid-row: 2; }
  .hero__juliste > .cta-rivi { grid-column: 1; grid-row: 3; }
  .hero__juliste > .hero__lead {
    grid-column: 2;
    grid-row: 1 / 4;
    align-self: center;
    max-width: 40ch;
    margin: 0;
  }
}

.hero__kuva {
  position: relative;
  height: clamp(190px, 30svh, 420px);   /* 34svh was tried 6.8. and reverted: at the
     1280x640 demo stage the band is on its 220px floor either way, so the extra
     height bought nothing where it is presented, and it spent the fold budget
     that DOD row 13's injected longest lead needs on a tall phone.
     The floor then went 220 → 190 (6.8., final round) — a STATED break of the
     synthesis's "photo band ≥220px phone". Flooring the hero lead at 13px and the CTA
     label at 12px (they rendered 11.6px and 9.7px at 320×568, against 17px body copy
     elsewhere) costs 25px of fold budget under DOD row 13's injected longest lead, and
     the poster rhythm was already sitting on its own 4px floors with nothing left to
     give. The synthesis says "text clamps first"; the lead inverted that for
     legibility, so the band pays. It only ever pays on a viewport SHORTER than 733px,
     where 30svh is under 220 anyway — every phone cell in the rig except 320×568
     (360×800 → 240px, 390×844 → 253px) is untouched. */
  overflow: hidden;
}
.hero__kuva::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--sauma);
  background: var(--musta-0);
  box-shadow: inset 0 3px 5px -1px rgba(0, 0, 0, 0.75);
  z-index: 2;
}
/* picture AND img are taken out of flow: a percentage height on the img alone resolves
   against the auto-height <picture>, so the image renders at its natural aspect, the band
   clips its TOP and object-position is silently ignored — the declared focal box never
   reaches the screen. Same defect and same fix as .osasto__kuva in sivut.css. */
.hero__kuva picture, .hero__kuva img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 49%;      /* phone crop focal box, MANIFEST §1 */
}

@media (min-width: 768px) {
  .hero__kuva { height: clamp(245px, 28svh, 420px); }
  .hero__kuva img { object-position: 50% 58%; }  /* desktop focal box */
}

/* short phones: the type clamps, the band holds its floor (synthesis, hero) */
@media (max-width: 767px) {
  .hero .kicker { font-size: min(13px, 1.9svh); letter-spacing: 0.16em; }
  .hero h1 { font-size: min(2.25rem, 4.4svh); max-width: none; }
  /* svh-scaled with a legibility FLOOR. Without the max() the 320×568 cell rendered the
     lead at 11.6px and the CTA label at 9.7px against 17px body copy elsewhere — a money
     button whose label is the smallest type on the page. The floors are 13px / 12px; the
     photo band yields the difference (see .hero__kuva's short-phone rule). */
  .hero .lead { font-size: max(13px, min(1.0625rem, 2.05svh)); line-height: 1.4; max-width: 42ch; }
  .hero .cta { min-height: 44px; font-size: max(12px, min(0.875rem, 1.7svh)); padding: 0 1.1rem; }
  .hero .cta-rivi { gap: 8px 12px; }
}

/* ---------------------------------------------------------------- bands ----
   Drawer fronts: full-width bands, alternating photo side. Not cards, not
   numbered rows — a run of fronts sharing one carcase line. */
.banda {
  display: grid;
  align-items: center;
}

.banda__teksti {
  display: flex;
  flex-direction: column;
  gap: clamp(18px, 2.2vw, 32px);
  padding: var(--huone-pysty) var(--reuna);
  max-width: 640px;
  margin-inline: auto;
  width: 100%;
}
.banda__teksti .cta-rivi { margin-top: 6px; }

.banda__kuva { position: relative; aspect-ratio: 3 / 2; }
.banda__kuva picture, .banda__kuva img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media (min-width: 1024px) {
  .banda { grid-template-columns: 1fr 1fr; }
  .banda__teksti { padding-inline: clamp(32px, 5vw, 80px); margin-inline: 0; }
  .banda--oikea .banda__teksti { justify-self: end; }
  .banda--vasen .banda__kuva { order: -1; }
  .banda__kuva { aspect-ratio: auto; min-height: 560px; }
}

/* a frame that closes its room gets the room's own air below it (3.9.): inset on
   the sides and flush at the bottom read as a frame hanging into the seam. Padding
   on the ROOM, not margin on the frame: a last child's bottom margin collapses
   through a padding-less section and the air lands between the rooms, where the
   body ground paints it black (measured: a full-width musta band under Mallisto) */
.room:has(> .elevaatio:last-child, > .nauha:last-child,
          > .rivi:last-child, > .setti:last-child) { padding-bottom: var(--huone-pysty); }

/* the waterfall joint — ONE per page: the mass field crosses the tonal seam
   instead of stopping at it (synthesis: a seam treatment, never free-floating).
   It hangs into the LIGHT room below, where the crossing is actually visible. */
.vesiputous {
  z-index: 2;
  margin-bottom: calc(-1 * var(--ylitys));
}

/* ------------------------------------------------------- elevation panels --
   Product panels of varying widths sharing one plinth line, varjosauma seams
   between them. The dark backing IS the seam: the gaps are the shadow gaps.

   REPAIRED 6.8. with Mallisto's .juoksu, same law — the two runs are one
   component in two lengths. Before: flex-end plus `aspect-ratio: 4/3` on varying
   widths gave three plate heights (348/281/322 at 1390) with voids above the
   short ones, and `cover` with no blend cropped the shots' pure #FFF ground into
   a bright rectangle on the panel's warmer paperi. */
.elevaatio {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sauma);
  padding: var(--sauma);
  background: var(--musta-0);
  align-items: stretch;
  /* inset to the gutter (3.9.): full-bleed frames ending 8px from the viewport edge
     were the main reason the page read as crammed — same law as .kentta */
  width: calc(100% - 2 * var(--reuna));
  max-width: var(--mitta);
  margin-inline: auto;
  --tuotekuva: clamp(240px, 62vw, 440px);   /* one plate height for the whole run */
}

/* varying widths on one plinth line — a cabinet elevation, not a card grid.
   Variation by flex-BASIS: grow factors below 1 (the old 0.92) absorb only that
   fraction of the free space, so a lone panel stopped short — 374/365/369 px at
   390. Grow is 1 now, every line fills. Three-across and its 812px threshold are
   unchanged: the owner asked to keep that desktop reading. */
.paneeli {
  flex: 1 1 100%;
  min-width: 0;                 /* WebKit: min-width:auto lets the panel photo's
                                   intrinsic width push the run past the viewport */
  display: flex;
  flex-direction: column;
  background: var(--paperi);
  color: var(--musta-0);
  --muste: var(--musta-0);
  --hiljainen: color-mix(in oklab, var(--musta-0) 78%, var(--paperi));
}
@media (min-width: 560px) {
  .elevaatio { --tuotekuva: clamp(250px, 38vw, 440px); }
  .paneeli:nth-child(2n+1) { flex-basis: calc(54% - 4.6px); }
  .paneeli:nth-child(2n+2) { flex-basis: calc(46% - 4.6px); }
}
@media (min-width: 812px) {
  .elevaatio { --tuotekuva: clamp(250px, 24vw, 520px); }
  .paneeli:nth-child(3n+1) { flex-basis: calc(36% - 6px); }
  .paneeli:nth-child(3n+2) { flex-basis: calc(30% - 6px); }
  .paneeli:nth-child(3n+3) { flex-basis: calc(34% - 6px); }
}

/* the plate: one height for every panel, object centred by `contain`, its white
   ground multiplied onto the panel's paperi so plate and text read as one
   surface. Same mechanism as Mallisto's .perhe__kuva. */
.paneeli__kuva {
  position: relative;
  height: var(--tuotekuva);
  background: var(--paperi);
  isolation: isolate;
}
.paneeli__kuva picture, .paneeli__kuva img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.paneeli__kuva img { mix-blend-mode: multiply; }
.paneeli__teksti {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  gap: 10px;
  padding: clamp(18px, 2vw, 30px);
  /* no hairline: it was the only line cutting the panel into two rectangles */
}
.paneeli .teksti { font-size: 0.9375rem; max-width: 34ch; }

/* ---------------------------------------------------------------- strip ----
   Projektit: two delivered spaces, one shared baseline, seams between. */
.nauha {
  display: grid;
  gap: var(--sauma);
  padding: var(--sauma);
  background: var(--musta-0);
  width: calc(100% - 2 * var(--reuna));   /* inset to the gutter, 3.9. — see .elevaatio */
  max-width: var(--mitta);
  margin-inline: auto;
}
@media (min-width: 700px) {
  .nauha { grid-template-columns: 1.2fr 1fr; }
}
.nauha figure { position: relative; margin: 0; }
.nauha img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.nauha figcaption {
  position: absolute;
  left: 0;
  bottom: 0;
  padding: 8px 14px;
  background: var(--musta-0);
  color: var(--saarni);
  font-family: var(--font-ui);
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* ------------------------------------------------------------ testimonial --
   Slot only. No quote renders until real client quotes arrive (ban 1); the
   italic voice is reserved for them. */
.slotti {
  display: flex;
  flex-direction: column;
  gap: 14px;
  align-items: flex-start;
  padding: clamp(28px, 4vw, 48px);
  border: 1px solid var(--viiva);
  box-shadow: inset 0 var(--sauma) 0 -6px color-mix(in oklab, var(--musta-0) 55%, transparent);
  max-width: 720px;
}
.slotti__label {
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 0.8125rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--hiljainen);
}
.sitaatti {
  font-family: var(--font-reading);
  font-style: italic;
  font-size: clamp(1.375rem, 2vw, 1.625rem);
  line-height: 1.45;
}

/* ------------------------------------------------------------ contact band - */
.yhteysbanda {
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 2vw, 26px);
  align-items: flex-start;
}
.yhteysbanda .h2 { max-width: 18ch; }
/* the testimonial slot lives in the contact room since 3.9. (it was a room of its own
   holding one label — one of the stripes that made the page read as crammed) */
.yhteysbanda + .slotti { margin-top: clamp(48px, 6vw, 88px); }

/* ---------------------------------------------------------------- footer --- */
.jalka { padding-block: clamp(48px, 6vw, 88px) clamp(28px, 3vw, 44px); }
.jalka__merkki { width: clamp(180px, 32vw, 300px); margin-bottom: clamp(28px, 4vw, 48px); }

.jalka__ristikko {
  display: grid;
  align-items: end;
  gap: clamp(28px, 4vw, 56px);
}
@media (min-width: 800px) {
  .jalka__ristikko { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); }
}

.jalka h2 {
  font-size: 0.8125rem;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--hiljainen);
  margin-bottom: 12px;
}

.jalka address {
  font-style: normal;
  display: flex;
  flex-direction: column;
}
.jalka address p { margin: 0; }
.jalka__label { color: var(--hiljainen); }

.jalka a {
  display: inline-block;
  padding: 11px 0;
  color: var(--linkki);
  text-decoration: none;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.3em;
  text-decoration-color: color-mix(in oklab, var(--tammi) 50%, transparent);
  transition: color var(--mikro) var(--ease), text-decoration-color var(--mikro) var(--ease);
}
.jalka a:hover { text-decoration-color: var(--tammi); }

.merkkirivi {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(18px, 3vw, 34px);
}
.merkkirivi img { height: 40px; width: auto; }  /* badge ceiling, lead decision */

.jalka__sivut {
  margin-top: clamp(24px, 3vw, 40px);
  padding-top: 18px;
  border-top: 1px solid var(--viiva);
  font-family: var(--font-ui);
  font-size: 0.8125rem;
  letter-spacing: 0.06em;
  color: var(--hiljainen);
}
.jalka__sivut a { padding-inline: 8px; letter-spacing: 0.06em; }

.jalka__luonnos {
  margin-top: 14px;
  font-family: var(--font-ui);
  font-size: 0.8125rem;
  color: var(--hiljainen);
}

/* ---------------------------------------------------------------- motion ---
   Constants: 500ms cubic-bezier(0.22,1,0.36,1), 12px rise + fade, 70ms group
   stagger, once-only. INVERTED GATING LAW: the hidden state exists only where
   BOTH prefers-reduced-motion: no-preference AND html.motion hold. No JS, no
   IntersectionObserver, or reduced motion → the page is simply painted. */
@media (prefers-reduced-motion: no-preference) {
  html.motion .rv {
    opacity: 0;
    transform: translateY(var(--nousu));
  }
  html.motion .rv--in {
    opacity: 1;
    transform: none;
    transition:
      opacity var(--kesto) var(--ease) var(--d, 0ms),
      transform var(--kesto) var(--ease) var(--d, 0ms);
  }
}

/* explicit reduce block — belt and braces over the gate above, and the thing
   tools/check-motion.js reads: every transition resolves to 0s */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0s !important;
    transition-delay: 0s !important;
    animation-duration: 0s !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  html.motion .rv { opacity: 1; transform: none; }
}

/* ----------------------------------------------------------------- print ---
   Save-as-PDF rendered blank pages: print media matches
   `prefers-reduced-motion: no-preference`, so every block that had not yet been
   revealed by the IntersectionObserver was still at opacity 0 — measured on
   mallisto, 13 of 13 .rv elements invisible. The reveal is a screen affordance;
   on paper everything is simply painted. The fixed header would otherwise
   repeat over the first page's content. */
@media print {
  html.motion .rv,
  html.motion .rv--in {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .paalkki { position: static; }
  .paalkki__valikko { display: none; }
}

/* ============================================================================
   Woods Helsinki — page sections for the sub-pages
   Loaded ONLY by the pages that use it, after styles.css. Reason it is not
   appended to styles.css (stated deviation, wave A): index.html measures
   594,746 B against the 600,000 B platform gate — 5.2 kB of headroom — so any
   growth in the SHARED sheet breaks a page that never uses a byte of it.
   Same tokens, same varjosauma, same motion constants; nothing here restyles
   the shell. Wave B appends here too.
   ============================================================================ */

/* ------------------------------------------------- section-opener (osasto) --
   The Etusivu hero grammar at section scale: a dark poster room over a
   full-width photo band, the two joined by the varjosauma. Deliberately NOT
   .hero — a content page opens, it does not hold the fold hostage, and reusing
   .hero would mean unwinding its svh clamps in three breakpoint blocks. */
.osasto__sisus {
  display: flex;
  flex-direction: column;
  gap: clamp(12px, min(2vw, 2.4svh), 26px);
  max-width: var(--mitta);
  width: 100%;
  margin-inline: auto;
  padding-inline: var(--reuna);
  /* svh-gated so a SHORT viewport yields rhythm and the opener's photo band
     crosses into the fold. Measured 6.8. at 1280x640: the band began 42px
     below the fold on Keittiot and 33px below on Projektit, so both money
     pages opened on what three cold reads called an empty black half-screen —
     the picture existed, it just missed the demo viewport. Tall viewports keep
     their spacing (the svh term only wins when it is the smaller value). */
  padding-top: calc(var(--palkki) + clamp(38px, min(8vw, 9svh), 104px));
  padding-bottom: clamp(24px, min(5vw, 5.5svh), 72px);
}

/* the poster word keeps .juliste's own 96-120px scale, which is only possible because
   it takes a FULL-measure row of its own: boxed into a half column it wrapped
   "KEITTIÖT" to "KEITTIÖ / T" and had to be cut to 85px to fit (both measured). */
.osasto__sana { color: var(--muste); }

.osasto__teksti {
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.4vw, 18px);
}
/* subordinate to the poster: the poster is the display object, the H1 names the page */
.osasto__teksti h1 {
  font-size: clamp(1.5rem, 2.6vw, 2.125rem);
  letter-spacing: -0.004em;
  max-width: 20ch;
}
.osasto__teksti .lead {
  max-width: 52ch;
  color: color-mix(in oklab, var(--muste) 84%, var(--ground));
}
.osasto__teksti .teksti { max-width: 52ch; }

.osasto__kuva {
  position: relative;
  height: clamp(200px, 30svh, 380px);
  overflow: hidden;
}
.osasto__kuva::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--sauma);
  background: var(--musta-0);
  box-shadow: inset 0 3px 5px -1px rgba(0, 0, 0, 0.75);
  z-index: 2;
}
/* picture AND img are taken out of flow: a percentage height on the img alone resolves
   against the auto-height <picture>, so the image renders at its natural aspect, the
   band clips its TOP and object-position is silently ignored — the declared focal box
   never reaches the screen. Measured in Chromium, not only WebKit. */
.osasto__kuva picture, .osasto__kuva img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 49%;      /* phone focal box, MANIFEST §1 */
}

@media (min-width: 768px) {
  .osasto__kuva { height: clamp(260px, 34svh, 430px); }
  .osasto__kuva img { object-position: 50% 58%; }   /* desktop focal box */
}

/* poster across the measure, the reading block indented to the right half — the
   offset is the composition, and it leaves the poster its full scale */
@media (min-width: 900px) {
  .osasto__sisus {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: clamp(32px, 5vw, 88px);
    row-gap: clamp(20px, 2.8vw, 38px);
    align-items: end;
  }
  .osasto__sana { grid-column: 1 / -1; }
  .osasto__teksti { grid-column: 2; }
}

/* Mallisto's opener has no photo band — a studio object stranded in a 1280px-wide
   strip reads as an empty slab (measured). Instead the pair is set INTO the dark room
   as a square paperi block with a recessed edge: the inlay at object scale, and the
   thing that fills the poster room's otherwise empty upper-left quarter. */
.upotus {
  position: relative;
  aspect-ratio: 1 / 1;
  width: 100%;
  max-width: 360px;
  background: var(--paperi);
  isolation: isolate;
  box-shadow: inset 0 var(--sauma) 9px -6px rgba(0, 0, 0, 0.8);
}
.upotus picture, .upotus img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* the studio frames' ground is measured pure #FFFFFF, paperi is #F6F3ED — laid flat
     they read as a pasted rectangle. multiply maps the shot's white exactly onto the
     block's paperi, so the object sits ON the surface instead of on a patch of it.
     A display blend, not a new asset: the file on disk is untouched. */
  mix-blend-mode: multiply;
}

@media (min-width: 900px) {
  .osasto--upotus .osasto__sisus { grid-template-areas: 'sana sana' 'upotus teksti'; }
  .osasto--upotus .osasto__sana { grid-area: sana; }
  .osasto--upotus .osasto__teksti { grid-area: teksti; }
  .osasto--upotus .upotus { grid-area: upotus; justify-self: start; }

  /* Any opener carrying an inlay gets the same placement, not just Mallisto's
     variant: without it auto-placement pushes the block to a THIRD row (the
     cursor has already passed row-2/col-1 once .osasto__teksti is placed in
     col 2), which is what left Keittiot and Projektit with an empty lower-left
     quadrant while the picture sat below the fold. Measured 6.8. */
  .osasto__sisus:has(.upotus) { grid-template-areas: 'sana sana' 'upotus teksti'; }
  .osasto__sisus:has(.upotus) .osasto__sana { grid-area: sana; }
  .osasto__sisus:has(.upotus) .osasto__teksti { grid-area: teksti; }
  .osasto__sisus:has(.upotus) .upotus { grid-area: upotus; justify-self: start; align-self: end; }
}

/* ------------------------------------------------------- two-column intro --
   Statement left, reading measure right. Single column below 1024px, where a
   34px statement and a 58ch paragraph cannot share a line. */
.parivaljakko {
  display: grid;
  gap: clamp(18px, 3vw, 34px);
}
.parivaljakko h2 { max-width: 22ch; }

@media (min-width: 1024px) {
  .parivaljakko {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: clamp(40px, 5vw, 96px);
    align-items: start;
  }
}

/* ----------------------------------------------------------- photo rows ----
   Frames sharing one baseline with varjosauma between them; the dark backing
   IS the seam, exactly as in the Etusivu elevation. */
.rivi {
  display: grid;
  gap: var(--sauma);
  padding: var(--sauma);
  background: var(--musta-0);
  width: calc(100% - 2 * var(--reuna));   /* inset to the gutter, 3.9. — see .elevaatio */
  max-width: var(--mitta);
  margin-inline: auto;
}
.rivi figure {
  position: relative;
  margin: 0;
  aspect-ratio: 3 / 2;
}
.rivi picture, .rivi img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.rivi--pysty figure { aspect-ratio: 4 / 5; }

@media (min-width: 760px) {
  .rivi--pari { grid-template-columns: 1.4fr 1fr; }
  .rivi--pari figure { aspect-ratio: auto; }        /* the row's own height rules */
  .rivi--pari { min-height: clamp(300px, 44svh, 520px); }
  .rivi--pysty figure { aspect-ratio: auto; }
}

/* the cabin set: one landscape holding the left column, two details stacked
   right. Absolute-positioned images throughout — a percentage height on an img
   inside a content-sized row is the WebKit trap that inflates hero rows. */
.setti {
  display: grid;
  gap: var(--sauma);
  padding: var(--sauma);
  background: var(--musta-0);
  width: calc(100% - 2 * var(--reuna));   /* inset to the gutter, 3.9. — see .elevaatio */
  max-width: var(--mitta);
  margin-inline: auto;
}
.setti figure {
  position: relative;
  margin: 0;
  aspect-ratio: 3 / 2;
}
.setti figure.setti__pysty { aspect-ratio: 4 / 5; }
.setti picture, .setti img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media (min-width: 860px) {
  .setti { grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); }
  /* the type selector is carried on purpose: `.setti figure` sets aspect-ratio at
     0,1,1, and a bare `.setti__iso` would lose to it — the tall frame would then
     keep 3/2 while spanning two rows and demand a 1037px column (measured). */
  .setti figure.setti__iso { grid-row: span 2; aspect-ratio: auto; }
  .setti figure.setti__pysty { aspect-ratio: 3 / 2; }
}

/* -------------------------------------------------------- full photo field -
   One image carrying a whole room, no text over it (text-over-photo is a
   rejected option and a contrast liability).
   .kentta insets it to the measure ON PURPOSE: this field is the page's one
   waterfall joint, and a full-bleed band crossing a full-bleed seam shows
   nothing — there is no edge for the crossing to read against. */
.kentta { padding: clamp(40px, 6vw, 88px) var(--reuna) 0; }
.kentta .kuvakentta { max-width: var(--mitta); margin-inline: auto; }
.kuvakentta { position: relative; }
.kuvakentta img {
  width: 100%;
  height: clamp(240px, 46svh, 540px);
  object-fit: cover;
  object-position: 50% 52%;
}

/* --------------------------------------------------------- spec grooves ----
   The three service lists read as routed grooves in one surface: the panels
   carry the room's own ground and the varjosauma between them is the only
   drawn line. Distinct from the paperi elevation on purpose — this is joinery
   in the surface, that one is fronts on a carcase. */
.speksit {
  display: grid;
  gap: var(--sauma);
  /* no padding: the dark ground shows ONLY through the gaps, so the run reads as
     grooves routed into one surface rather than as a framed plate */
  background: var(--musta-0);
}
.speksi {
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.4vw, 18px);
  padding: clamp(22px, 2.8vw, 38px);
  background: var(--ground);
}

@media (min-width: 820px) {
  /* auto-fit, not repeat(3): Muut tilat carries two lists and a fixed three-track
     grid left the third track showing as a bare dark slab. Collapsing empty tracks
     keeps the same 3-up run on Keittiöt and survives the N±1 case on both. */
  .speksit { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
}

/* vertical list with hairline rules — the reading form for a short spec set */
.lista {
  display: flex;
  flex-direction: column;
  font-family: var(--font-reading);
  color: var(--muste);
}
.lista li {
  padding-block: 7px;
  border-top: 1px solid var(--viiva);
}
.lista li:first-child { border-top: 0; padding-top: 0; }

/* dot-grammar run for long name lists. The separator lives INSIDE its own
   item, so a wrapped line can never start with a floating dot. */
.lista--nimet {
  flex-direction: row;
  flex-wrap: wrap;
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  letter-spacing: 0.02em;
  color: var(--hiljainen);
  max-width: 76ch;
}
.lista--nimet li {
  padding-block: 4px;
  border-top: 0;
}
.lista--nimet li:not(:last-child)::after {
  content: '·';
  margin-inline: 0.55em;
  color: color-mix(in oklab, var(--muste) 45%, var(--ground));
}

/* ------------------------------------------------ Mallisto: cabinet run ----
   Panels of varying widths on a kalkki room, varjosauma seams between them. Not
   a card grid — widths vary — but each ROW is one solid run: one top edge, one
   bottom edge, one plate height. Repaired 6.8., owner cold read; the broken
   declarations and their measurements are in that commit. */
/* kalkki above and below: graft 5 keeps the ROOM kalkki and only the run's own
   backing dark, so the wall has to sit on something visible. Padding on the room,
   not margin on the run — a margin on an only child collapses straight out of the
   section and the kalkki never paints (measured). */
.juoksu-huone { padding-block: clamp(34px, 5vw, 76px); }

/* 34px of padding-block at 390, dark rooms above and below: the kalkki read as a
   floating stripe. Below 1024 the run is inset to the page gutter so the beige
   wraps it on four sides and reads as a surface; at >=1024 the band is >=51px and
   the run spans wall to wall, the reading the owner liked. */
@media (max-width: 1023.98px) {
  .juoksu-huone { padding-inline: var(--reuna); }
}

.juoksu {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;         /* was flex-end: the ragged tops were the defect */
  gap: var(--sauma);
  padding: var(--sauma);
  background: var(--musta-0);
  /* ONE plate height for every panel (see .perhe__kuva), retuned per wrap tier,
     never per panel: just under that tier's narrowest panel width. */
  --tuotekuva: clamp(240px, 72vw, 470px);        /* one across */
}

.perhe {
  flex: 1 1 100%;
  min-width: 0;                 /* WebKit: min-width:auto lets the studio shot's
                                   intrinsic width push the run past the viewport */
  display: flex;
  flex-direction: column;
  background: var(--paperi);
  color: var(--musta-0);
  --muste: var(--musta-0);
  --hiljainen: color-mix(in oklab, var(--musta-0) 78%, var(--paperi));
  --linkki: var(--musta-0);
}

/* Widths by flex-BASIS on a ladder whose per-row count divides six (1/2/3/6): at
   4-up and 5-up the remainder row cannot read as a run. Percentages still differ
   inside a tier. Each basis subtracts a little over its gap share so a row never
   rounds onto a second line; grow stays 1, so an N±1 row still fills. */
@media (min-width: 640px) {
  .juoksu { --tuotekuva: clamp(250px, 38vw, 460px); }
  .perhe:nth-child(2n+1) { flex-basis: calc(54% - 4.6px); }
  .perhe:nth-child(2n+2) { flex-basis: calc(46% - 4.6px); }
}
@media (min-width: 1160px) {
  .juoksu { --tuotekuva: clamp(300px, 27vw, 480px); }
  .perhe:nth-child(3n+1) { flex-basis: calc(36% - 6px); }
  .perhe:nth-child(3n+2) { flex-basis: calc(30% - 6px); }
  .perhe:nth-child(3n+3) { flex-basis: calc(34% - 6px); }
}
@media (min-width: 1860px) {
  /* six across: panels narrower than the 3-up plate, so the plate follows down */
  .juoksu { --tuotekuva: clamp(250px, 13.5vw, 370px); }
  .perhe:nth-child(6n+1) { flex-basis: calc(18% - 7.2px); }
  .perhe:nth-child(6n+2) { flex-basis: calc(15.5% - 7.2px); }
  .perhe:nth-child(6n+3) { flex-basis: calc(17% - 7.2px); }
  .perhe:nth-child(6n+4) { flex-basis: calc(15% - 7.2px); }
  .perhe:nth-child(6n+5) { flex-basis: calc(17.5% - 7.2px); }
  .perhe:nth-child(6n+6) { flex-basis: calc(17% - 7.2px); }
}

.perhe__kuva {
  position: relative;
  /* FIXED height, not `aspect-ratio: 1/1` (owner 6.8.: the plates must be the same
     size in every panel). A square plate takes its height from its own panel's
     width and the widths vary on purpose, so six panels gave six plate heights —
     the mechanism behind both the ragged tops and the phone complaint. This
     subordinates graft 5's "the tops do not line up"; beside it, not reversed. */
  height: var(--tuotekuva);
  background: var(--paperi);
  isolation: isolate;
}
.perhe__kuva picture, .perhe__kuva img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;          /* studio object on its native white, never cut */
}
.perhe__kuva img { mix-blend-mode: multiply; }   /* white → paperi, see .osasto__kuva--upotus */

.perhe__teksti {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;               /* takes the panel's slack so the CTA can sit on it */
  gap: 10px;
  padding: clamp(16px, 1.8vw, 26px);
  /* no rule between plate and text: one surface. paperi measures 246,243,238 to
     the multiply's 245,242,237, both engines. */
}
.perhe__nimi {
  font-family: var(--font-ui);
  font-weight: 300;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  line-height: 1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.perhe .spec--laji { color: var(--hiljainen); letter-spacing: 0.11em; }
.perhe .teksti { font-size: 0.9375rem; max-width: 38ch; }
/* the CTA falls to the panel's bottom, so the links share one line — the plinth
   line the run is named for, carried by type */
.perhe .cta-rivi { margin-top: auto; padding-top: 4px; }

/* --------------------------------------------- Projektit: project rooms ----
   Ground per project (the ground is read off that project's own photography),
   the kicker set in the room's own margin — never over the photo. */
.kohde { display: grid; }

.kohde__label {
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 2.4vw, 28px);
  align-items: flex-start;
  padding: clamp(30px, 4.5vw, 60px) var(--reuna) clamp(14px, 2vw, 22px);
}

.kohde__kuva {
  position: relative;
  height: clamp(240px, 44svh, 500px);
}
.kohde__kuva picture, .kohde__kuva img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The label is a MARGIN and has to look inhabited: 270px of column, 104px of it
   padding, the kicker against its top edge, 220-470px of bare ground under it —
   deliberate on musta-1, unfinished on kalkki/paperi (~1390, RAVINTOLATILA).
   Centred now, 34px off the inner padding. */
@media (min-width: 1024px) {
  .kohde {
    grid-template-columns: minmax(200px, 0.26fr) minmax(0, 1fr);
    align-items: stretch;
  }
  .kohde__label {
    justify-content: center;
    padding: clamp(44px, 5vw, 76px) clamp(14px, 1.4vw, 22px) clamp(44px, 5vw, 76px) var(--reuna);
  }
  .kohde__kuva { height: clamp(400px, 60svh, 640px); }
  .kohde--peili { grid-template-columns: minmax(0, 1fr) minmax(200px, 0.26fr); }
  .kohde--peili .kohde__label {
    order: 2;
    padding-inline: clamp(14px, 1.4vw, 22px) var(--reuna);
  }
}


/* ---------------------------------------------------------- room heading ---
   A room that opens with a heading and a measure, then hands off to a list. */
.huone-otsikko {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(16px, 2vw, 26px);
  margin-bottom: clamp(28px, 4vw, 48px);
}

/* ============================================================================
   WAVE B — Muut tilat & kiintokalusteet · Tarina · Yhteystiedot (6.8.2026)
   Appended, not woven in: the shell and the wave-A sections are untouched, and
   every block below says why it is not one of theirs. Same tokens, same
   varjosauma, same motion constants.
   ============================================================================ */

/* ------------------------------------------------ poster word, second rank ---
   Muut tilat is the only opener whose name does not fit one poster line, so the
   poster carries two ranks inside ONE h1: the nav label at full .juliste scale,
   "ja kiintokalusteet" under it. Ratio, not taste: the second string is 18 chars
   against the first's 10, so at 0.45em it sets ~0.8 of the top line's width and
   stays visibly subordinate — at 0.6em it renders WIDER than the header it sits
   under and the hierarchy inverts (measured both). Tracking opens as the size
   drops, the way caps want. .osasto--johto (the old sentence-case treatment) was
   deleted with this block, not left dead. */
.osasto__jatko {
  display: block;
  font-size: 0.45em;
  letter-spacing: 0.028em;
  margin-top: clamp(2px, 0.5vw, 8px);
  color: color-mix(in oklab, var(--muste) 76%, var(--musta-0));
}

/* ---------------------------------------------------------- N±1 photo run ---
   "lisää kuvia projekteista, tuotteista jne tulee myöhemmin" (forwarded brief)
   is a layout requirement, not a footnote: this run has to look deliberate at
   two frames and at four. flex-grow carries the width variation and the line
   fills at any count — the Mallisto cabinet-run law without the panels. The
   frames share one line height (stretch), so the run keeps a straight plinth
   line and a straight top no matter what shapes land in it. */
.kuvarivi {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sauma);
  padding: var(--sauma);
  background: var(--musta-0);
}
.kuvarivi figure {
  position: relative;
  margin: 0;
  flex: 1 1 300px;
  min-width: 0;               /* WebKit: min-width:auto lets the photo's intrinsic
                                 width push the run past the viewport */
  aspect-ratio: 3 / 2;
}
.kuvarivi figure:nth-child(3n+1) { flex-grow: 1.16; }
.kuvarivi figure:nth-child(3n+2) { flex-grow: 0.9; }
.kuvarivi figure:nth-child(3n+3) { flex-grow: 1.04; }
.kuvarivi picture, .kuvarivi img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* a slot label that follows a spec run needs the room's own rhythm between them —
   scoped to this pairing so no wave-A page moves */
.speksit + .slotti { margin-top: clamp(28px, 4vw, 48px); }

/* ------------------------------------------------------- letterbox bands ----
   tarina-saha derives from a 1600×441 slider crop and the manifest binds it to
   horizontal bands only — never a portrait or square fill. min-height keeps the
   band off a 107px sliver at 390px; cover then takes the crop off the sides,
   which a panorama survives. */
.raita { position: relative; margin: 0; }
/* .raita's `margin: 0` shorthand lives in sivut.css, which loads AFTER styles.css,
   so at equal specificity it silently killed .vesiputous's negative margin-bottom
   and the band stopped crossing its seam — the motif was inert on every .raita
   that carried it (measured 6.8.: computed margin-bottom 0px on Tarina while
   Etusivu's grid-item band read −64px). Restated here at .raita.vesiputous so the
   crossing wins where it is declared. */
.raita.vesiputous { margin-bottom: calc(-1 * var(--ylitys)); }
.raita img {
  width: 100%;
  aspect-ratio: 1600 / 441;
  min-height: 150px;
  max-height: 420px;
  object-fit: cover;
  object-position: 50% 50%;
}

/* --------------------------------------------- the UPOTUS anchor as a photo -
   woods-helsinki-oy.jpg IS the concept — the wordmark CNC-inlaid, ash into
   walnut — and the client's own file ceiling is 558² (manifest §6). It is set
   as a square inlay well under that ceiling, never blown across a band. No
   multiply blend here: that maps a studio shot's pure white onto paperi, and
   this frame is a photographed walnut surface that would go muddy. */
.upotus--kuva {
  background: var(--musta-0);
  max-width: min(100%, 340px);   /* manifest ceiling is 558 CSS px; 340 also keeps the
                                    opener's lead inside the first view at 1280×640 */
}

/* an opener that carries an object as well as a poster word starts higher up: with
   the standard opener padding the Tarina lead fell out of the first view at 640.
   Bottom floor 44px, not 24px: thinner than that the pähkinä under the inlay read
   at 390 as a brown line between two darks. Top padding untouched (fold). */
.osasto--tiivis .osasto__sisus {
  padding-top: calc(var(--palkki) + clamp(24px, 4vw, 56px));
  padding-bottom: clamp(44px, 4vw, 56px);
}
.upotus--kuva picture,
.upotus--kuva img {
  object-fit: cover;
  mix-blend-mode: normal;   /* the multiply above is set on BOTH picture and img —
                               unsetting only the img leaves the wrapper multiplying
                               the photo into the block's ground (measured: near-black) */
}

/* --------------------------------------------------------- reading room -----
   Tarina's kalkki room is the one place on the site with a real reading
   measure: Literata at ~62ch, the serif doing exactly the job the role
   discipline reserves for it. */
.luku {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(18px, 2.4vw, 30px);
}
.luku .teksti {
  max-width: 62ch;
  font-size: 1.0625rem;
  line-height: 1.7;
  color: var(--muste);
}

/* the PRH facts as a spec register, never as story prose (copy-deck 6.3 / T7).
   Rides on .lista--nimet so the dot separator stays inside its own item. */
/* The spec register inherits .lista--nimet's dot separator, which lives in the
   PRECEDING item — so when the three facts wrapped, a `·` was left hanging at a line
   end with nothing after it. The synthesis is explicit: "Dot separator inline between
   words ONLY — never floating", and a dot at a line end is exactly a floating dot.
   Three short items, so the fix is to stop wrapping instead of restyling the dot:
   below the width where the register fits on one line it stacks and draws no
   separators at all; above it, one nowrap line where every dot has words on both
   sides. (The long customer list on Projektit keeps the wrapping treatment — 26 names
   cannot be a single line at any width, and there the dot reads as a list separator.) */
.lista--spec {
  font-family: var(--font-ui);
  font-size: 0.8125rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hiljainen);
  max-width: 70ch;
  column-gap: 0.55em;
}
/* The separator moves from the PRECEDING item to the FOLLOWING one. Same visual
   rhythm, but the dot is now glued to the item it introduces, so a wrap can only ever
   put it at the START of a line, never leave it stranded at the end of one. (A nowrap
   single line was tried first and reverted: harden's injected longest spec row then
   overflowed the viewport by 74px at 1280×640 — no-overflow outranks a typographic
   nicety, and this version keeps both.) */
.lista--spec li:not(:last-child)::after { content: none; }
.lista--spec li:not(:first-child)::before {
  content: '·';
  margin-inline-end: 0.55em;
  color: color-mix(in oklab, var(--muste) 45%, var(--ground));
}

/* ------------------------------------------------------ Yhteystiedot room ---
   One kalkki room, contact first: the wish list's stated priority is that the
   contact details are found "helposti ja nopeasti", so the facts read before
   the email address is the largest interactive object on the site. */
.yhteysruudukko {
  display: grid;
  gap: clamp(40px, 6vw, 76px);
  align-items: start;
}
@media (min-width: 960px) {
  .yhteysruudukko {
    grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr);
    column-gap: clamp(48px, 6vw, 104px);
  }
}

.yhteyskortti {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(22px, 3vw, 34px);
}

/* the email at display scale, with the tammi inlay under the words; hover fills
   the recess upward instead of moving the object */
.sposti {
  display: inline-flex;
  align-items: center;
  min-height: 56px;
  padding-right: 2px;
  font-family: var(--font-ui);
  font-weight: 300;
  font-size: clamp(1.5rem, 4.4vw, 2.5rem);
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--muste);
  text-decoration: none;
  box-shadow: inset 0 -3px 0 0 var(--tammi);
  transition: box-shadow var(--mikro) var(--ease);
}
.sposti:hover { box-shadow: inset 0 -0.42em 0 0 var(--tammi); }

.tiedot {
  display: grid;
  gap: 16px;
  font-style: normal;
  max-width: 46ch;
}
.tiedot p { margin: 0; }
.tiedot__label {
  display: block;
  font-family: var(--font-ui);
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hiljainen);
  margin-bottom: 2px;
}

/* the small honest slot: same object as the testimonial slot, at note scale */
.slotti--pieni {
  padding: clamp(16px, 2vw, 22px);
  max-width: 44ch;
}

/* --------------------------------------------------------------- the form ---
   Cosmetic until go-live (no woods tenant exists yet; the honest line says so).
   Native validation is left completely intact — ?demo adds values and touches
   nothing else. The fields are the varjosauma at input scale: recesses routed
   into the plate, lit from above, the same joint as the room seams. */
.lomake {
  display: grid;
  gap: clamp(14px, 1.8vw, 20px);
  padding: clamp(22px, 3vw, 38px);
  background: var(--kalkki-syva);
  box-shadow: inset 0 var(--sauma) 9px -6px rgba(0, 0, 0, 0.75);
}
.lomake__kentta {
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.lomake__kentta label {
  font-family: var(--font-ui);
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hiljainen);
}
.lomake input,
.lomake textarea {
  width: 100%;
  min-height: 48px;
  padding: 12px 14px;
  font-family: var(--font-reading);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--muste);
  background: color-mix(in oklab, var(--kalkki-syva) 91%, var(--musta-0));
  border: 0;
  border-radius: 0;
  appearance: none;              /* iOS Safari paints its own inset shading and radius on
                                    text controls; the recess here is the varjosauma, not
                                    the platform's */
  -webkit-appearance: none;
  box-shadow: inset 0 3px 5px -2px rgba(0, 0, 0, 0.55);
  transition: background-color var(--mikro) var(--ease), box-shadow var(--mikro) var(--ease);
}
.lomake textarea { min-height: 136px; resize: vertical; }
.lomake input:hover,
.lomake textarea:hover { background: color-mix(in oklab, var(--kalkki-syva) 85%, var(--musta-0)); }

/* the focus ring must not swallow the recess: the light-room focus rule sets
   box-shadow, so the inset is restated here or the field goes flat while focused */
.lomake input:focus-visible,
.lomake textarea:focus-visible {
  box-shadow: inset 0 3px 5px -2px rgba(0, 0, 0, 0.55), 0 0 0 5px var(--tammi);
}

.lomake__rivi {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 22px;
  margin-top: 4px;
}
/* Submit lands here: a draft form delivers nowhere, so there is no receipt to show and
   none is invented — focus moves to the line that says so. Without a visible state the
   presenter clicking the money button in front of the client saw nothing happen at all.
   The state hangs off :focus, NOT :focus-visible: the element is focused
   programmatically after a MOUSE click, which :focus-visible deliberately does not
   match. Treatment = the varjosauma at label scale — the line drops into a routed
   recess with the tammi notch on its leading edge and its ink steps up to full --muste.
   The padding and the transparent notch are permanent so focusing shifts nothing. */
.lomake__luonnos {
  font-family: var(--font-ui);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--hiljainen);
  max-width: 42ch;
  padding: 10px 14px;
  border-left: var(--sauma) solid transparent;
  transition:
    background-color var(--mikro) var(--ease),
    border-color var(--mikro) var(--ease),
    color var(--mikro) var(--ease);
}
.lomake__luonnos:focus {
  color: var(--muste);
  border-left-color: var(--tammi);
  background: color-mix(in oklab, var(--kalkki-syva) 88%, var(--musta-0));
  box-shadow: inset 0 3px 5px -2px rgba(0, 0, 0, 0.55);
}

/* the page headline sits just under the email in scale on purpose: the contact
   address is the object the wish list asks to be found first, and a 64px H1 over
   a 40px link would put the eye in the wrong place */
.yhteyshuone h1 {
  font-size: clamp(2rem, 4.2vw, 3rem);
  max-width: 18ch;
  margin-bottom: clamp(30px, 4.5vw, 52px);
}

.lomake__otsikko {
  font-family: var(--font-ui);
  font-weight: 300;
  font-size: clamp(1.25rem, 2.2vw, 1.5rem);
  line-height: 1.15;
  letter-spacing: -0.004em;
  margin-bottom: 4px;
}

/* -------------------------------------------------- header over a dark room -
   styles.css lets the paperi header go transparent until the first scroll,
   which is right over a light opener and wrong over a dark one (dark ink on a
   dark room). :has() reads the opening room, so the fix costs the chrome no
   markup — the header block stays byte-identical across all seven pages. It
   lives here and not in styles.css because index.html (the only page that skips
   this sheet) opens light. Dark openers are the pages not yet flipped (3.9.). */
html:not(.no-js):not(.scrolled) body:has(main > .room--musta:first-child,
                                         main > .room--musta-1:first-child,
                                         main > .room--pahkina:first-child) .paalkki {
  background: var(--ground);
  border-bottom-color: var(--viiva);
}

/* the opening paragraph of a content room carries lead scale: Muut tilat has no
   H2 in the deck, so without a size step the room opened at body weight and read
   as a footnote. Modifier, not a bare :first-child rule — Mallisto and Tarina use
   .parivaljakko for two paragraphs of equal rank and must not move. */
.parivaljakko--johto .teksti:first-child {
  font-size: clamp(1.0625rem, 1.1vw + 0.7rem, 1.375rem);
  line-height: 1.5;
  color: var(--muste);
  max-width: 34ch;
}

/* skip link: off-screen until focused */
.ohitus {
  position: absolute;
  left: 8px;
  top: -60px;
  z-index: 100;
  padding: 10px 16px;
  background: var(--paperi);
  color: var(--musta-0);
}
.ohitus:focus { top: 8px; }

/* headings break only where a soft hyphen says so: auto hyphenation split
   "puusepänverstas" mid-word in the h1 */
h1, h2, h3, .juliste { hyphens: manual; }

/* a row of names keeps every item on one baseline; .lista li:first-child is for
   the stacked lists */
.lista--nimet li:first-child { padding-top: 4px; }

.ei-loydy { padding-top: calc(var(--palkki) + 48px); min-height: 60vh; }

/* Tietosuojaseloste */
.tietosuoja { padding-top: calc(var(--palkki) + 48px); }
.tietosuoja .sisus { max-width: 760px; }
.tietosuoja h1 { margin: 0 0 8px; }
.tietosuoja__pvm { margin: 0 0 24px; font-family: var(--font-ui); font-size: 0.875rem; color: var(--hiljainen); }
.tietosuoja__tiedot { display: grid; gap: 12px; margin: 32px 0 48px; padding: 24px 0; border-block: 1px solid var(--viiva); }
.tietosuoja__tiedot dt { font-family: var(--font-ui); font-size: 0.75rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--hiljainen); }
.tietosuoja__tiedot dd { margin: 2px 0 0; }
.tietosuoja__tiedot a, .lomake__tietosuoja a { color: inherit; text-underline-offset: 0.25em; }
.tietosuoja__osio { margin-top: 40px; }
.tietosuoja__osio h2 { font-size: 1.375rem; margin: 0 0 12px; }
.tietosuoja__osio ul { padding-left: 1.2em; }
.lomake__tietosuoja { margin: 0; font-size: 0.875rem; color: var(--hiljainen); }

/* wood options read as a sentence, not a tracked caps string */
.perhe .spec--laji { text-transform: none; letter-spacing: 0; font-size: 0.875rem; }
