/* ═══════════════════════════════════════════════════════════════════════
   NORDEVO · Uuden rakentaja.
   Paperivalo: bright editorial paper, but the page opens in the dark. The
   mark is drafted BY HAND as an architectural drawing in the dark room:
   hand-ruled guides (wavering, overshooting, compass turns overlapping
   their starts), a pencil-weight hand sketch of the name, the six member
   strokes inking over the sketch, the period's r9 circle called for and
   the ember stamped into it — then the whole scaffolding lifts away and
   the exact production mark stands alone. Scrolling through the period is
   the dive INTO the home. Rooms,
   not sections: photo moods with shadow to write in, a reading light that
   walks the words, place names as the deepest parallax layer, and logo
   specimen rows. No datelines; the light is a motif, never a clock.
   No side ornaments: type, photos and hairlines carry the page alone.
   Structure (2026-08-11): hero → sanat → (the syöksy: the words fly past,
   the claim arrives as a close-up and settles, 2026-08-17) → miksi (the
   type room, on paper) →
   THE DARK PASSAGE: grand (the stage: full-bleed house darkens, lit rooms
   float past with lag, then its receipt) → jula → stadion (two simpler
   rooms, same grammar) → valonpaluu (the seam of light opens back into
   paper) → konserni (logo specimen rows) → tekijät → doors.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── The two families, served from the site itself since 2026-10-02 (they came
   from Google Fonts before: a third-party request on every page that also held
   the first paint). The files in /fonts/ are Google's own variable woff2s,
   Familjen Grotesk v11 and Newsreader v26, and the rules below are Google's
   stylesheet word for word except for the address and the dropped Vietnamese
   subset, so matching and rendering are unchanged: one face per weight for
   Newsreader (300-600 roman, 300-400 italic, all from the same variable file),
   a 400-600 range for the grotesk. Both are under the SIL Open Font License
   (the texts lie beside the files). The pages preload the two latin romans. ── */
@font-face { font-family:'Familjen Grotesk'; font-style:italic; font-weight:400 600; font-display:swap; src:url(/fonts/familjen-grotesk-v11-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:'Familjen Grotesk'; font-style:italic; font-weight:400 600; font-display:swap; src:url(/fonts/familjen-grotesk-v11-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:'Familjen Grotesk'; font-style:normal; font-weight:400 600; font-display:swap; src:url(/fonts/familjen-grotesk-v11-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:'Familjen Grotesk'; font-style:normal; font-weight:400 600; font-display:swap; src:url(/fonts/familjen-grotesk-v11-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:'Newsreader'; font-style:italic; font-weight:300; font-display:swap; src:url(/fonts/newsreader-v26-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:'Newsreader'; font-style:italic; font-weight:300; font-display:swap; src:url(/fonts/newsreader-v26-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:'Newsreader'; font-style:italic; font-weight:400; font-display:swap; src:url(/fonts/newsreader-v26-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:'Newsreader'; font-style:italic; font-weight:400; font-display:swap; src:url(/fonts/newsreader-v26-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:'Newsreader'; font-style:normal; font-weight:300; font-display:swap; src:url(/fonts/newsreader-v26-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:'Newsreader'; font-style:normal; font-weight:300; font-display:swap; src:url(/fonts/newsreader-v26-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:'Newsreader'; font-style:normal; font-weight:400; font-display:swap; src:url(/fonts/newsreader-v26-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:'Newsreader'; font-style:normal; font-weight:400; font-display:swap; src:url(/fonts/newsreader-v26-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:'Newsreader'; font-style:normal; font-weight:500; font-display:swap; src:url(/fonts/newsreader-v26-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:'Newsreader'; font-style:normal; font-weight:500; font-display:swap; src:url(/fonts/newsreader-v26-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:'Newsreader'; font-style:normal; font-weight:600; font-display:swap; src:url(/fonts/newsreader-v26-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:'Newsreader'; font-style:normal; font-weight:600; font-display:swap; src:url(/fonts/newsreader-v26-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; }

:root {
  --paper:#faf5ec;
  --ink:#221c12;
  --faded:#5a5142;
  --muted:#746956;
  --oranssi:#c25a17;                          /* low-sun orange */
  --sun:var(--oranssi);                       /* the accent on paper: orange (back 2026-09-30, Anton: orange on light, mint on dark) */
  --piste:var(--sun);                        /* logo dot = the accent */
  --spruce:#1f4736;                           /* spruce green (subpage scenes)    */
  --cream:#f2ecdd;                            /* type on dark                     */
  --amber:var(--minttu);                     /* legacy alias: kicker on dark = mint */
  --minttu:#9fd6a9;                          /* accent on dark: Nordewood mint (nordewood.fi --usva) */
  --himmea:#c6ba9e;                           /* unlit word on paper              */
  --disp:"Familjen Grotesk",-apple-system,"Helvetica Neue",Arial,sans-serif;
  --sans:var(--disp);                          /* sama groteski myös pikkukapiteeleissa */
  /* the light switch: 0 = the dark passage's ground, 1 = paper. valoFrame
     writes it HERE, on the root, so every dark section brightens as one
     surface and no edge can open between them mid-switch. */
  --v:0;
  /* both windows derive from --v alone, so they belong beside it: the
     type flip (--vt) and the late relaxation of muted browns (--vs).
     They used to sit on .paatos, which was fine while .paatos was the
     only lit thing on screen — with the passage down to one kohde the
     Grand receipt and the nav are inside the switch too. */
  /* the flip window narrowed .20 → .12 on 2026-09-04. Cream and ink must
     cross the ground's own lightness somewhere — the crossing cannot be
     designed away, only spent quickly — and it used to be spent on a
     screen still holding the rooms' photographs. With one kohde there is
     nothing on screen but ground and type at that instant, so the dead
     point read as the whole page going blank. Half the window, half the
     frames near zero contrast; the accent dot (mint → sun, both mid
     lightness) stays visible right through it as the anchor. */
  --vt:clamp(0, calc((var(--v) - .44) / .12), 1);
  --vs:clamp(0, calc((var(--v) - .62) / .38), 1);
  /* the dark passage's ground, mixed once and reused: every dark surface
     in the passage paints from this, so no edge can open between them */
  --maa:color-mix(in oklab, #0b0906, var(--paper) calc(var(--v) * 100%));
  /* cream that inverts to ink on the type's own window */
  --vmuste:color-mix(in oklab, var(--cream), var(--ink) calc(var(--vt) * 100%));
  /* the passage accent crossing mint → sun with the flip */
  --vacc:color-mix(in oklab, var(--minttu), var(--sun) calc(var(--vt) * 100%));
}

@property --dot {
  syntax:'<color>'; inherits:true; initial-value:#221c12;
}

* { margin:0; padding:0; box-sizing:border-box; }
html { background:var(--paper); scroll-behavior:smooth; scroll-padding-top:76px; }
body {
  background:var(--paper); color:var(--ink);
  font-family:"Newsreader", Georgia, serif; font-optical-sizing:auto;
  -webkit-font-smoothing:antialiased; overflow-x:hidden;
}
::selection { background:var(--sun); color:#fff6ea; }
img { display:block; }
a { color:inherit; }
a:focus-visible { outline:2px solid var(--sun); outline-offset:3px; }

/* ACCENT BY GROUND (Anton 2026-09-30): orange on paper, mint in the dark.
   Every dark room re-points --sun (and the logo dot) at the mint, so the
   rules inside keep saying var(--sun). The nav crosses with the page's
   light switch; a control already over paper goes orange at once. */
#hero, .saumaovi, footer.loppu .laatta, .no-js .lpaneeli, .pteksti,
.vihrea, .kpaatos { --sun:var(--minttu); --piste:var(--minttu); }
.nav.inv { --sun:color-mix(in oklab, var(--minttu), var(--oranssi) calc(var(--nav-type-light) * 100%));
  --piste:var(--sun); }
.nav.dark-fixed { --sun:var(--minttu); --piste:var(--minttu); }
.nav.inv .on-paper { --sun:var(--oranssi); --piste:var(--oranssi); }

/* page-to-page: same-origin navigations crossfade (progressive enhancement) */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration:.45s; }

.kick { font-family:var(--sans); font-weight:500; font-size:11.5px; letter-spacing:.2em;
  text-transform:uppercase; color:var(--faded); }

/* reveals — staggered inside any .stag group */
.js .rv { opacity:0; transform:translateY(18px); }
.js .rv.on { opacity:1; transform:none;
  transition:opacity 1.25s cubic-bezier(.22,1,.36,1), transform 1.25s cubic-bezier(.22,1,.36,1); }
.js .stag > .rv.on:nth-child(2) { transition-delay:.07s }
.js .stag > .rv.on:nth-child(3) { transition-delay:.14s }
.js .stag > .rv.on:nth-child(4) { transition-delay:.21s }
.js .stag > .rv.on:nth-child(5) { transition-delay:.28s }
.js .stag > .rv.on:nth-child(6) { transition-delay:.35s }
.js .stag > .rv.on:nth-child(7) { transition-delay:.42s }
.js .stag > .rv.on:nth-child(8) { transition-delay:.49s }
.js .stag > .rv.on:nth-child(9) { transition-delay:.56s }

/* line-mask reveal (2026-08-10): display lines rise out of their own baseline
   mask, one after the other — plain ink, only the arrival moves. JS splits at
   fonts-ready and unwraps back to plain text after playing; an element that
   turns .on before splitting falls back to a quiet fade. Display lines only,
   body text keeps .rv. */
.js .lr { opacity:0; }
.js .lr.lsplit, .js .lr.on { opacity:1; }
.js .lr.on:not(.lsplit) { transition:opacity 1.25s cubic-bezier(.22,1,.36,1); }
.lr .lrLine { display:block; overflow:hidden; padding-bottom:.15em; margin-bottom:-.15em; }
.lr .lrIn { display:block; transform:translateY(116%); }
.lr.on .lrIn { transform:none; transition:transform 1s cubic-bezier(.22,1,.36,1); }
.lr.on .lrLine:nth-child(2) .lrIn { transition-delay:.09s; }
.lr.on .lrLine:nth-child(3) .lrIn { transition-delay:.18s; }
.lr.on .lrLine:nth-child(4) .lrIn { transition-delay:.27s; }
.lr.on .lrLine:nth-child(5) .lrIn { transition-delay:.36s; }

/* the arrow whose tail stretches — a hairline that grows on hover */
.nuoli { display:inline-flex; align-items:center; margin-left:.5em; vertical-align:.08em; }
.nuoli .nvarsi { display:block; width:1.2em; height:1px; background:currentColor;
  transition:width .5s cubic-bezier(.22,1,.36,1); }
.nuoli .nkarki { display:block; width:.42em; height:.42em; flex:none;
  border-top:1px solid currentColor; border-right:1px solid currentColor;
  transform:rotate(45deg); margin-left:-.42em; }
a:hover .nuoli .nvarsi, a:focus-visible .nuoli .nvarsi { width:2.05em; }

/* ═══ the button · .nappi (2026-09-14, Anton's pick from design-directions/cta-2026-09) ═══
   A hairline pill with the stamped period after the word. On hover the
   period swells and fills the pill, the word goes ink on mint. Nothing
   else moves. The dot is a real inline flex item aligned to the text
   baseline, so it sits where a period sits. --nacc is the accent (the
   front page receipt rides --vacc through the light switch).
   Markup: <a class="nappi"><span class="nteksti">Sana</span><span class="npiste" aria-hidden="true"></span></a> */
.nappi { --nacc:var(--sun); position:relative; display:inline-flex; align-items:baseline; gap:.36em;
  isolation:isolate; overflow:hidden; border-radius:999px; box-sizing:border-box;
  border:1px solid color-mix(in oklab, currentColor, transparent 62%);
  padding:.66em 1.05em .7em .95em; margin:0; background:none; cursor:pointer;
  font-family:var(--disp); font-weight:500; font-size:clamp(18px,1.6vw,24px);
  letter-spacing:-.02em; line-height:1; color:inherit; text-decoration:none; white-space:nowrap;
  /* the word turns ink as the fill reaches it */
  transition:color .3s ease .18s, border-color .6s ease; }
.nappi.pieni { font-size:clamp(14.5px,1.2vw,17px); letter-spacing:-.01em; padding:.72em 1.15em .78em 1.1em; }
.nappi .nteksti { position:relative; z-index:1; }
.nappi .npiste { display:inline-block; width:.3em; height:.3em; flex:none; border-radius:50%;
  background:var(--nacc); z-index:0; transform-origin:50% 50%;
  /* the fill is the period growing: near-linear timing so the circle is
     seen crossing the word, not a snap (a fast-start ease at this scale
     factor covers the pill in the first two frames) */
  transition:transform .75s cubic-bezier(.5,.05,.3,1); }
.nappi:hover, .nappi:focus-visible { color:var(--ink); border-color:var(--nacc); outline:none; }
.nappi:hover .npiste, .nappi:focus-visible .npiste { transform:scale(90); }
/* .rv on the same element would replace the transition list; restate both */
.js .nappi.rv.on { transition:opacity 1.25s cubic-bezier(.22,1,.36,1), transform 1.25s cubic-bezier(.22,1,.36,1),
  color .3s ease .18s, border-color .6s ease; }
@media (prefers-reduced-motion:reduce) { .nappi, .nappi .npiste { transition:none; } }
/* a message on its way (ui.js busy): one click, one mail */
.nappi[aria-disabled="true"] { opacity:.55; pointer-events:none; }

/* One wording when scripts run, another when they do not (2026-10-02): the
   forms send from the page with a script and prepare a mail in the visitor's
   own program without one, so the button and the small print say which. The
   body carries no-js until its first inline script swaps it for js, so the
   right one stands from the first paint and nothing is rewritten on load. */
.no-js .vain-js, .js .vain-nojs { display:none !important; }

/* parenthetical count beside a kicker — a quiet inventory note */
/* (.lkm — the ( n / 3 ) inventory count — was removed 2026-08-12 with the
   passage's counter: three-item arithmetic made the company read small.) */

/* the light under the arriving door — marks each passage into a dark room */
.viiru { position:absolute; top:0; left:0; right:0; height:2px; z-index:5; opacity:0;
  background:linear-gradient(90deg, rgba(159,214,169,0), #9fd6a9 18% 82%, rgba(159,214,169,0));
  transform:scaleX(.25); transform-origin:center; pointer-events:none; }
.viiru.on { animation:viiruPass 2.1s cubic-bezier(.22,1,.36,1) forwards; }
@keyframes viiruPass { 0% { opacity:0; transform:scaleX(.25); } 45% { opacity:.95; }
  100% { opacity:.38; transform:scaleX(1); } }

/* ═══ NAV · liquid glass ═══
   A separate optical surface above the page. The SVG lens in nav-glass.js
   refracts the live backdrop along the straight edges; type stays sharp. */
.nav {
  --nav-paper:rgba(250,245,236,.28);
  --nav-dark:rgba(11,9,6,.24);
  --nav-tint:var(--nav-paper);
  --nav-edge-reflection:rgba(255,255,255,.055);
  --nav-light:var(--v); --nav-type-light:var(--vt); --nav-ink:var(--vmuste);
  --glass-x:28%; --glass-y:0%;
  position:fixed; top:0; left:0; right:0; z-index:90;
  display:flex; align-items:center; justify-content:space-between;
  min-height:56px; padding:18px 26px 14px; border-radius:0;
  opacity:0; transform:translateY(-10px); pointer-events:none;
  background:var(--nav-tint);
  -webkit-backdrop-filter:blur(10px) saturate(1.25);
  backdrop-filter:blur(10px) saturate(1.25);
  box-shadow:inset 0 -1px 1px rgba(255,255,255,.22);
  transition:opacity .6s ease, transform .6s ease, background-color .5s ease, box-shadow .5s ease;
}
.nav.show { opacity:1; transform:none; pointer-events:auto; }
.nav[data-glass="refracted"] {
  -webkit-backdrop-filter:url("#nordevo-nav-lens") saturate(1.18);
  backdrop-filter:url("#nordevo-nav-lens") saturate(1.18);
}
/* Broad, quiet reflection on the surface, brighter where the pointer is.
   The lower inner shade gives the straight glass edge a little depth. */
.nav::before, .nav::after {
  content:""; position:absolute; inset:0; border-radius:inherit; pointer-events:none;
}
.nav::before {
  background:radial-gradient(ellipse 300px 90px at var(--glass-x) var(--glass-y),
      rgba(255,255,255,.22), transparent 75%),
    linear-gradient(180deg, rgba(255,255,255,.12), transparent 38% 70%, var(--nav-edge-reflection));
  box-shadow:inset 0 -2px 4px rgba(11,9,6,.08);
}
/* A single light-catching edge under the full-width glass pane. */
.nav::after {
  top:auto; height:1px; opacity:1; transition:opacity .5s ease;
  background:linear-gradient(90deg, rgba(255,255,255,.5), rgba(255,255,255,.12) 48%,
    rgba(255,255,255,.35));
}
.nav > * { position:relative; z-index:1; }
/* Keep the site's existing light-switch clock, including the portrait tube. */
.nav.inv { --nav-tint:color-mix(in oklab, var(--nav-dark),
    var(--nav-paper) calc(var(--nav-light) * 100%));
  --nav-edge-reflection:color-mix(in oklab, rgba(242,236,221,.008),
    rgba(255,255,255,.055) calc(var(--nav-light) * 100%));
  box-shadow:inset 0 -1px 1px color-mix(in oklab, transparent,
    rgba(255,255,255,.22) calc(var(--nav-light) * 100%)); }
/* In the dark, the refracted backdrop defines the seam. Bring the drawn
   highlight back only as the page's existing light switch reaches paper. */
.nav.inv::after { opacity:var(--nav-light); }
/* The hero and footer stay dark even when an earlier room's switch is at 1.
   Local aliases also leave the portrait tube's inline colour clock intact. */
.nav.dark-fixed { --nav-light:0; --nav-type-light:0; --nav-ink:var(--cream); }
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))) {
  .nav { --nav-paper:var(--paper); --nav-dark:#0b0906; }
  .nav::before, .nav::after { display:none; }
}
@media (prefers-reduced-transparency:reduce), (forced-colors:active) {
  .nav, .nav[data-glass="refracted"] {
    --nav-paper:var(--paper); --nav-dark:#0b0906;
    -webkit-backdrop-filter:none; backdrop-filter:none; box-shadow:none;
    border-bottom:1px solid currentColor;
  }
  .nav::before, .nav::after { display:none; }
}
/* hairline wordmark: min legible width ~140px (handoff rule), so 24px tall */
.nav .logoS { display:block; height:24px; width:auto; color:var(--ink); --dot:var(--piste);
  transition:color .5s ease; }
.nav .logoS svg { display:block; height:100%; width:auto; overflow:visible; }
.nav ul { display:flex; gap:34px; list-style:none; }
.nav ul a {
  font-family:var(--sans); font-size:11.5px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--ink); text-decoration:none;
  padding:12px 0; background:linear-gradient(90deg,var(--sun),var(--sun)) no-repeat 0 calc(100% - 7px)/0 1px;
  transition:color .4s ease, background-size .5s cubic-bezier(.22,1,.36,1);
}
.nav ul a:hover { color:var(--ink); background-size:100% 1px; }
/* Aalto (2026-09-14): the letters of a hovered name hop one after another,
   a ripple running left to right while the mint hairline draws under them.
   Letters come from nordevo.js / alasivu.js wrapping each character in .k.
   The same ripple runs through the eight words of #sanat, the hop scaled
   to the display size through --hop (see .sana .k below). */
.nav ul a .k { display:inline-block; }
@media (hover:hover) and (prefers-reduced-motion:no-preference) {
  .nav ul a:hover .k { animation:navAalto .55s cubic-bezier(.3,0,.2,1) calc(var(--i, 0) * 32ms) both; }
}
@keyframes navAalto {
  0%   { transform:translateY(0); }
  35%  { transform:translateY(var(--hop, -4px)); }
  100% { transform:translateY(0); }
}
.nav.inv .logoS { color:var(--nav-ink); }
.nav.inv ul a { color:var(--nav-ink); text-shadow:0 1px 6px color-mix(in oklab, rgba(11,9,6,.45), transparent calc(var(--nav-type-light) * 100%)); }
.nav.inv ul a:hover { color:var(--nav-ink); }
.nav .menuBtn { display:none; }
/* The partner opening uncovers paper one strip at a time. Its controls
   change individually as that paper reaches them, even with a dark photo
   still under the other end of the transparent bar. */
.nav.inv .logoS.on-paper, .nav.inv ul a.on-paper, .nav.inv .menuBtn.on-paper {
  color:var(--ink); text-shadow:none;
}

/* mobile menu */
.menu {
  position:fixed; inset:0; z-index:120; background:var(--paper);
  display:flex; flex-direction:column; justify-content:center; padding:0 11vw;
  opacity:0; pointer-events:none; transition:opacity .5s ease;
}
.menu.open { opacity:1; pointer-events:auto; }
.menu a { font-family:var(--disp); font-size:clamp(32px,8.5vw,51px); font-weight:500;
  text-decoration:none; padding:2.2vh 0; letter-spacing:-.025em;
  opacity:0; transform:translateY(16px); transition:opacity .7s ease, transform .7s ease; }
.menu.open a { opacity:1; transform:none; }
.menu.open a:nth-child(2){transition-delay:.06s} .menu.open a:nth-child(3){transition-delay:.12s}
.menu.open a:nth-child(4){transition-delay:.18s} .menu.open a:nth-child(5){transition-delay:.24s}
.menu.open a:nth-child(6){transition-delay:.3s} .menu.open a:nth-child(7){transition-delay:.36s}
.menu.open a:nth-child(8){transition-delay:.42s}
.menu .close { position:absolute; top:18px; right:22px; font-family:var(--sans);
  font-size:12px; letter-spacing:.16em; text-transform:uppercase; color:var(--faded);
  background:none; border:0; cursor:pointer; padding:10px; opacity:1; transform:none; }

/* ═══ 1 · HERO: the dark cold open — the period arrives alone, the name
   rises out of the baseline behind it, and through the full stop into
   the home ═══ */
#hero { height:240vh; position:relative; z-index:2; }
#hero .hold { position:sticky; top:0; height:100vh; overflow:hidden; background:#0b0806; }

/* the room before the light: one built seam — NOT the room the period
   dives into (.oikkuna .kuva opens dark → light). It breathes in while
   the name rises, fully home as the N lands. */
.yo { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.js .yo { transform:scale(1.045);
  animation:yosettle 9s cubic-bezier(.22,1,.36,1) 1.7s forwards; }
@keyframes yosettle { to { transform:scale(1); } }
/* narrow viewports crop the print hard on the horizontal: pull the frame
   left so the seam stays in it — centred, a phone sees only dark wall */
@media (max-width:860px) { .yo { object-position:26% center; } }

.masthead { position:absolute; inset:0; z-index:2; display:flex; flex-direction:column;
  align-items:center; justify-content:center; text-align:center; }

/* the arrival (2026-09-30): the period alone, then the name rises out
   of the baseline behind it. nordevo.js scrubs each letter's transform
   and the period's opacity/scale, and buildFinish clears them back to
   THIS rest: the whole mark. No-JS, ?s and reduced motion start here. */
.cmark { position:relative; width:min(88vw,1080px); aspect-ratio:649/108; }
.cmark svg { position:absolute; inset:0; width:100%; height:100%; overflow:visible; display:block; }
.cmark .st { stroke:#f2ecdd; }
/* the period is placed, not drawn: until the build reaches it the dot
   rests at scale 0 (no-JS and reduced motion get it whole) */
.pd0 { transform-box:fill-box; transform-origin:center; }
.js:not(.static) .pd0 { transform:scale(0); }
.js .pd0.pop { transform:none; }
/* the letters wait under the baseline (#cRise cuts them) until the
   build lifts them; .valmis is the rest state */
.js:not(.static) .cmark:not(.valmis) .jasen .st { transform:translateY(108px); }
@keyframes dotpop { 0% { transform:scale(0); } 68% { transform:scale(1.28); } 100% { transform:scale(1); } }

.masthead .tag { margin-top:3.4vh; font-family:var(--disp); font-size:clamp(18px,1.9vw,25px);
  font-weight:500; letter-spacing:-.02em; color:rgba(242,236,221,.86); }
.masthead .front { position:absolute; bottom:4.5vh; left:0; right:0; text-align:center;
  font-size:13px; color:rgba(242,236,221,.58); letter-spacing:.05em; font-family:var(--sans); }

/* the room's light and the words run on the build's own clock: nordevo.js
   writes their opacity inline while the name rises (the words follow it
   up, the N lands at ~2.8s), and .levossa on #hero is the rest state */
.js:not(.static) #hero:not(.levossa) .yo,
.js:not(.static) #hero:not(.levossa) .masthead .tag,
.js:not(.static) #hero:not(.levossa) .masthead .front { opacity:0; }
@keyframes rise { from { opacity:0; transform:translateY(18px);} to { opacity:1; transform:none; } }

/* quiet cue: a drop of light runs down once the mark is whole */
.vihje { position:absolute; bottom:10.5vh; left:50%; width:1px; height:36px; overflow:hidden; }
.vihje::before { content:""; position:absolute; inset:0;
  background:linear-gradient(180deg, rgba(242,236,221,0), rgba(242,236,221,.62));
  transform:translateY(-100%); }
.js #hero.levossa .vihje::before, .static .vihje::before { animation:valuu 2.8s ease-in-out .4s infinite; }
@keyframes valuu { 0% { transform:translateY(-100%); opacity:0; } 30% { opacity:.9; }
  70%, 100% { transform:translateY(100%); opacity:0; } }

/* light bleeding around the ember — small, warm, breathing */
.halo { position:absolute; z-index:1; pointer-events:none; border-radius:50%; opacity:0; }
.halo i { position:absolute; inset:0; display:block; border-radius:50%;
  background:radial-gradient(circle, rgba(255,205,120,.32) 0%, rgba(255,199,110,.10) 42%, rgba(255,199,110,0) 66%);
  animation:halob 5.6s ease-in-out infinite; }
@keyframes halob { 0%,100% { opacity:.7; transform:scale(1); } 50% { opacity:1; transform:scale(1.1); } }

/* the world behind the full stop */
.oikkuna { position:absolute; inset:0; z-index:10; pointer-events:none; }
/* what the point opens into: the LIT room, deliberately a different one
   from the print above -- that contrast is the whole dive */
.oikkuna .kuva { position:absolute; inset:0; background:url('img/dining-window-light.jpg') center/cover no-repeat; }
.oikkuna .hehku { position:absolute; }
.oikkuna .hehku i { position:absolute; inset:0; display:block;
  background:radial-gradient(circle, rgba(255,231,178,.95) 16%, rgba(255,199,110,.42) 42%, rgba(255,199,110,0) 70%);
  animation:hengitys2 4.6s ease-in-out infinite; }
@keyframes hengitys2 { 0%,100% { opacity:1; } 50% { opacity:.7; } }
.js .oikkuna { -webkit-mask-image:radial-gradient(circle 0px at 50% 50%, #000 0, transparent 0);
  mask-image:radial-gradient(circle 0px at 50% 50%, #000 0, transparent 0); }

/* the landing: shadow falls into the bright room and the house greets you */
.oikkuna .varjo { position:absolute; inset:0; opacity:0;
  background:
    linear-gradient(0deg, rgba(10,8,6,.5) 0%, rgba(10,8,6,0) 38%),
    linear-gradient(74deg, rgba(10,8,6,.78) 0%, rgba(10,8,6,.42) 36%, rgba(10,8,6,0) 62%); }
.saapuminen { position:absolute; left:9vw; bottom:11vh; max-width:min(560px,80vw);
  color:#f6efe0; opacity:0; transform:translateY(14px); }
.saapuminen h2 { font-family:var(--disp); font-weight:500; font-size:clamp(30px,3.8vw,56px);
  letter-spacing:-.025em; line-height:1.1; }
.saapuminen p { margin-top:14px; font-size:clamp(15px,1.3vw,17.5px); line-height:1.7;
  color:rgba(246,239,224,.85); max-width:40ch; }

/* ═══ paper sections ═══ */
.paperi { position:relative; z-index:3; background:var(--paper); }

/* ═══ 2 · SANAT: six words, six sectors — the reading light walks the list ═══ */
/* in the syöksy the real scroll carries this section out (-100vh on the
   flight's clock); the JS adds onton leave on top — grow to 1.2 from the
   centre, sliding under #miksin paper, the words thinning to a dim —
   syoksyFrame writes the transform and opacity, rest = identity */
#sanat { padding:15vh 12vw 11vh; overflow:hidden; }
#sanat .wrap { position:relative; z-index:2; }
/* "Mitä rakennamme" is not a micro label here: it is the question the
   list answers, so it runs at twice the kicker scale (Anton 2026-09-10) */
#sanat .kick { font-size:clamp(16px,1.55vw,21px); letter-spacing:.17em; }
.sanalista { margin-top:5vh; display:block; }
.sana { display:block; width:fit-content; position:relative; text-decoration:none;
  font-family:var(--disp); font-weight:500; font-size:clamp(44px,7.2vw,112px);
  letter-spacing:-.03em; line-height:1.04; padding:1.35vh 0;
  color:color-mix(in oklab, var(--ink) calc(max(var(--t,1), var(--h,0)) * 100%), var(--himmea));
}
.sana:hover, .sana:focus-visible { --h:1; }
.sana:focus:not(:focus-visible) { outline:none; }
.sana:nth-child(2) { margin-left:5vw; }
.sana:nth-child(3) { margin-left:1.5vw; }
.sana:nth-child(4) { margin-left:8vw; }
.sana:nth-child(5) { margin-left:3vw; }
.sana:nth-child(6) { margin-left:9.5vw; }
.sana:nth-child(7) { margin-left:2.5vw; }
.sana:nth-child(8) { margin-left:11vw; }
/* the underline lives at the word's own baseline, never near the next row */
.sana .sw { white-space:nowrap; /* the letters are separate boxes (.k), never a line each */
  background:linear-gradient(90deg,var(--sun),var(--sun)) no-repeat 0 88%/0 2px;
  transition:background-size .55s cubic-bezier(.22,1,.36,1); }
.sana:hover .sw, .sana:focus-visible .sw, .sana:active .sw { background-size:100% 2px; }
/* the letters hop one after another under the pointer, the nav's aalto at
   display scale: nordevo.js wraps each letter of .sw in .k with --i */
.sana .k { display:inline-block; --hop:-.12em; }
@media (hover:hover) and (prefers-reduced-motion:no-preference) {
  .sana:hover .k, .sana:focus-visible .k { animation:navAalto .6s cubic-bezier(.3,0,.2,1) calc(var(--i, 0) * 30ms) both; }
}
/* the period stamps after the word lands — the mark's aperture as punctuation */
.sana .p { display:inline-block; transform:scale(0); transform-box:border-box;
  transform-origin:60% 60%; }
.sana.on .p { animation:dotpop .5s cubic-bezier(.3,1.6,.4,1) .55s forwards; }
.sana .p.aurinko { color:var(--sun); }
/* the closing line is staged, not a footnote (Anton 2026-09-10, pick A):
   display scale on the same left edge as Koteja., the period stamped in sun,
   and it stays dim until the reading light has run off the last word and
   landed on it — nordevo.js walks it with the .sana rows, so it shares their
   --t. Rest state (no JS, no --t) is full ink, like the words. */
#sanat .jalki { margin-top:11vh; max-width:none;
  font-family:var(--disp); font-weight:500; font-size:clamp(32px,4.6vw,74px);
  letter-spacing:-.03em; line-height:1.05;
  color:color-mix(in oklab, var(--ink) calc(var(--t,1) * 100%), var(--himmea)); }
#sanat .jalki .liitos { white-space:nowrap; }
#sanat .jalki .p { display:inline-block; color:var(--sun); transform:scale(0);
  transform-box:border-box; transform-origin:60% 60%; }
#sanat .jalki.on .p { animation:dotpop .5s cubic-bezier(.3,1.6,.4,1) .55s forwards; }
/* the stage the reader leaves: one built room full of daylight, wider
   than the text measure (negative margins against the section's 12vw),
   always visible like #grandin gtausta — no .plate machinery, no caption,
   no veils; the syöksy dims the img's brightness as the section flies */
.skatto { margin:10vh -8vw 0; }
.skatto img { width:100%; height:min(72vh,860px); object-fit:cover; }

/* ═══ 3 · MIKSI: the type room (uusinta 2026-08-11) — paper, not a dark
   photo room. The thesis is set enormous on the left, the lede hangs far
   right and low, one wide flat photograph carries the receipt, three
   promises run full width as movements separated by air alone, and the
   numbers close the section as a bare editorial row on clean paper. No
   background image, no veils, no rules: type and one photo only (the
   ember-tipped hairlines cut 2026-08-31 — the page keeps its one ruled
   register in #konserni). The section ENDS on paper so #grand's dark
   threshold cuts straight in. ═══ */
#miksi { position:relative; z-index:3; background:var(--paper); color:var(--ink);
  overflow:hidden; padding:0 0 17vh; }
/* the syöksy (2026-08-17→18; uusinta 2026-09-01, Antonin täsmennys:
   sivu PYSÄHTYY sanat-osion loppuun ja pienin tuore rullaus laukaisee
   koko siirtymän — onton resepti bundlesta luettuna: yhteinen 2.5s
   kello, bezier .42,0,.24,1, saapuva sisältö laskeutuu 5× lähikuvasta
   ja ensimmäinen rivi lentää lisäksi alhaalta paikalleen): väite saapuu
   LÄHIKUVANA ja lede 78vh alempaa (onton grid__item y:800→0) —
   työpöydän .js-lähtötila on lennon kaukopää (mitta 5 + leden 78vh:
   aina näkyvä, ensimmäinen ruutu) ja syoksyFrame kirjoittaa joka
   ruudun; laskeutunut = mitta 1, siirto 0. Ikkuna leikkaa lähikuvan
   ylivuodon ja kantaa mastheadin ylätyhjän (13vh — tiivistetty
   2026-09-01 kun kuitti muutti näyttämölle: yksi näytöllinen kantaa
   väitteen, leden JA kuvan). Mobiili, no-js, reduced-motion ja
   ?s-printti (.nvflat) ovat suoraa pinoa mitassa 1, joten media-lohko
   alla kantaa koko koneiston. */
.mikkuna { position:relative; overflow:hidden; padding-top:13vh; }
/* the whole 18vh of air lives on the WINDOW, outside the zoom unit, so
   the close-up's first local pixel is the kicker: no amplified blank at
   deep zoom. Origin x sits at 38%, on the claim itself: the words fill
   the frame and recede toward the middle (a dead 50% would stage the
   empty gutter between claim and lede at five times paper). */
.mlahi { transform-origin:38% 0; }
@media (min-width:861px) and (prefers-reduced-motion:no-preference) {
  /* in flight the window is a full-height stage (onton fixed 100vh
     container): without it the clip box is only as tall as the settled
     masthead and a mid-flight frame crops the claim away entirely */
  body.js:not(.nvflat) .mikkuna { min-height:100vh; }
  body.js:not(.nvflat) .mlahi { transform:scale(5); }
  /* the kuitti is onton grid__item (y:800→0): it starts its flight 78vh
     below its place inside the zoom unit and syoksyFrame flies it up.
     On the stage it rides fully formed — no io fade, no duotone
     develop, the caption standing — because the arriving screen is ONE
     composed page: claim, lede, photo (2026-09-01, the lede's flight
     moved onto the photo when it joined the stage) */
  body.js:not(.nvflat) .mlahi .mtodiste { transform:translateY(78vh); }
  body.js:not(.nvflat) .mlahi .plate .ph,
  body.js:not(.nvflat) .mlahi .plate figcaption { opacity:1; transform:none; transition:none; }
  body.js:not(.nvflat) .mlahi .plate .ph img { filter:none; transition:none; }
  /* the close-up carries its words with it: no io-fade under the zoom
     (the claim is present the whole flight, onto-style), and the period
     waits for the landing instead of the intersection */
  body.js:not(.nvflat) .mlahi .rv { opacity:1; transform:none; transition:none; }
  body.js:not(.nvflat) .mlahi .miso .p { animation:none; }
  body.js:not(.nvflat) .mlahi.landed .miso .p {
    animation:dotpop .5s cubic-bezier(.3,1.6,.4,1) .2s forwards; }
}
/* masthead: the claim left and huge, the lede far right and baseline-low */
.mhead { display:grid; grid-template-columns:minmax(0,1fr) minmax(280px,33%);
  gap:5vh 6vw; align-items:end; padding:0 12vw; }
.miso { font-family:var(--disp); font-weight:500; font-size:clamp(44px,7.4vw,124px);
  letter-spacing:-.032em; line-height:.94; margin-top:3vh; }
.miso .p { display:inline-block; transform:scale(0); transform-box:border-box;
  transform-origin:60% 60%; color:var(--sun); }
.miso.on .p { animation:dotpop .5s cubic-bezier(.3,1.6,.4,1) .6s forwards; }
.mlede { padding-bottom:1.1vh; }
.mlede p { font-size:clamp(15.5px,1.32vw,18px); line-height:1.75; color:var(--faded);
  max-width:34ch; }
.mlede .mkuitti { margin-top:2.4vh; font-family:var(--sans); font-size:12.5px;
  letter-spacing:.05em; line-height:1.7; color:var(--muted); }
/* the receipt photo: one wide plate hung right, cropped to a low strip
   so the WHOLE arriving page fits one viewport — claim, lede and kuva
   are one composition (2026-09-01, Anton: the section that comes up must
   contain the image; the valojuova band and its mtFrame machine were cut
   the same day). On the desktop stage the flight rules in the media
   block above carry it; the mobile stack keeps the shared plate grammar
   (io fade, duotone develops, caption rises). */
.mtodiste { width:min(68vw,980px); margin:6vh 8vw 0 auto; }
/* .plate.mtodiste outguns the PLATES block's height:auto (same class
   count would lose on source order) */
.plate.mtodiste .ph img { height:min(40vh,560px); object-fit:cover; }
/* the promises: full-width movements, air alone between them */
.vastuut { margin-top:15vh; padding:0 12vw; }
.vast { display:grid;
  grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr); gap:2.6vh 7vw;
  align-items:start; padding:8vh 0 8.5vh; }
.vlabel { font-family:var(--sans); font-weight:500; font-size:11.5px; letter-spacing:.2em;
  text-transform:uppercase; color:var(--muted); }
/* the three words (2026-09-09, Anton: the triad, and bigger "to make it more
   striking"). Single words, so the line runs to its column: Laadukkaasti. is
   5.73em at this tracking and the .92fr column is 31.74vw, so 5.3vw leaves a
   hair of air at the edge. Scales with the column, caps like the masthead. */
.vast h3 { margin-top:1.8vh; font-family:var(--disp); font-weight:500;
  font-size:clamp(44px,5.3vw,104px); letter-spacing:-.03em; line-height:1;
  max-width:13ch; }
.vteksti { font-size:clamp(15px,1.28vw,17.5px); line-height:1.78; color:var(--faded);
  max-width:46ch; padding-top:.4vh; }
/* the count: four numerals across the full measure, no furniture */
.mitattu { margin-top:16vh; padding:0 12vw; }
.mitattu h3 { font-family:var(--disp); font-weight:500; font-size:clamp(24px,2.7vw,42px);
  letter-spacing:-.025em; }
/* the columns are cut to their numerals, not to a fourth each: 100 000 needs
   room a bare 1fr never gives it */
.luvut { display:grid; grid-template-columns:1.5fr 1fr .7fr 1fr; gap:5vh 3.5vw;
  margin-top:7vh; }
.luku .iso { display:block; font-family:var(--disp); font-weight:400;
  font-size:clamp(38px,5.5vw,96px); letter-spacing:-.038em; line-height:1;
  font-variant-numeric:tabular-nums; white-space:nowrap; }
.luku .lisa { color:var(--sun); }
.luku .mista { display:block; margin-top:1.6vh; font-family:var(--sans); font-weight:500;
  font-size:11.5px; letter-spacing:.17em; text-transform:uppercase; color:var(--muted); }
.mitattu .fine { margin-top:7vh; font-size:14.5px; color:var(--muted); max-width:52ch; }

/* ═══ 3½ · KESKUSPUISTON GRAND: the stage (uusinta 3+, 2026-08-11 —
   Anton's spec: FULL-SCREEN image that scrolls normally and darkens,
   multiple images floating up. The full-bleed photo of the finished
   house keeps its daylight for a good while, then darkens quickly
   (brightness on the img, no veils; the darkening is a VIBE, not a time
   of day — never document it as dusk/evening). RUSKEASUO reads over the
   darkened house, then FOUR lit rooms float up from below at clearly
   different speeds (two on mobile), each easing after the scroll on its
   own clock so they keep drifting when the scroll stops. The floaters
   never use the .plate io-reveal machinery: always visible, .js
   enter-states matching the first frame. No pin. (The drone film is
   reserved for kohteemme.) ═══ */
/* GRAND ON KOKO KÄYTÄVÄ (2026-09-04): the two rooms that used to carry
   the passage from here to the light switch are gone, so this section
   inherits their three jobs. (1) Its ground is the SHARED mix, not a
   hardcoded #0b0906 — against a .paatos already lifting toward paper a
   fixed ground drew a ruled edge straight across the screen, the one
   thing the switch exists to avoid. (2) Air below the receipt (--kiito)
   before the closing stage arrives; it was a 34vh runway while the ramp
   rode the words, but since the lights only come up once the stage has
   pinned (2026-09-04) the receipt is always off screen by then, and the
   runway is plain section air again. (3) The receipt's type rides --vt
   like the closing words: cream left on a rising ground dissolves. */
#grand { position:relative; z-index:3; overflow:hidden;
  --kiito:10vh; padding-bottom:var(--kiito);
  background:var(--maa); color:var(--vmuste); }
/* the stage: the finished house full-bleed from the section's first
   pixel, scrolling with the page and darkening in grandFrame; the padded
   tail below the photo is the dark stretch the last rooms float through */
.gnayttamo { position:relative; z-index:2; padding-bottom:55vh; }
.gtausta { position:relative; z-index:1; height:185vh; overflow:hidden; }
.gtausta img { width:100%; height:100%; object-fit:cover; object-position:57% 50%;
  transform:scale(1.06); will-change:transform,filter; }
/* the house dissolves into the room floor: the bright street level would
   otherwise stay a washed band once the image has darkened */
.gtausta::after { content:""; position:absolute; left:0; right:0; bottom:0; height:38%;
  pointer-events:none;
  background:linear-gradient(180deg,
    color-mix(in oklab, var(--maa), transparent 100%) 0%,
    color-mix(in oklab, var(--maa), transparent 45%) 58%,
    var(--maa) 100%); }
/* the name is written on the sky: the section's only words. It rides
   gently with the house and its period stamps as the ember. */
.gnimi { position:absolute; z-index:2; top:14vh; left:12vw; margin:0;
  font-family:var(--disp); font-weight:500; font-size:clamp(44px,7vw,118px);
  letter-spacing:-.03em; line-height:1.02; white-space:nowrap;
  will-change:transform; }
.gnimi .p { display:inline-block; transform:scale(0); transform-box:border-box;
  transform-origin:60% 60%; color:var(--minttu); }
.gnimi.on .p { animation:dotpop .5s cubic-bezier(.3,1.6,.4,1) .75s forwards; }
/* the name is also the door to the chapter: nothing shows until hover */
.gnimi a, .gknimi a { text-decoration:none; padding-bottom:3px;
  background:linear-gradient(90deg,var(--minttu),var(--minttu)) no-repeat 0 100%/0 1px;
  transition:background-size .5s cubic-bezier(.22,1,.36,1); }
.gnimi a:hover, .gknimi a:hover { background-size:100% 1px; }
/* RUSKEASUO floats in the darkened half of the stage, deepest of all the
   layers: behind the rooms, above the photo it is written on */
.gaave { top:196vh; left:5vw; }
/* the lit rooms: below the fold until the image has darkened, then
   floating up clearly faster than the page, each at its own speed, with
   generous air between arrivals (travel per frame in grandFrame) */
.gkelluja { position:absolute; z-index:3; margin:0; will-change:transform; }
.gkelluja img { width:100%; height:auto; }
.gka { top:126vh; right:10vw; width:min(24vw,350px); }
.gkb { top:155vh; left:8vw;   width:min(26vw,380px); }
.gkc { top:189vh; left:41vw;  width:min(21vw,310px); }
.gkd { top:228vh; right:13vw; width:min(23vw,330px); }
/* enter-state before the first frame: (0.5 - vis) * speed at vis 0, so the
   first grandFrame never causes a visible jump (speeds 1.05 / 1.65 / 1.3 /
   2.2 — spread widened 2026-09-01, Anton: lisää vauhtia osalle, selvemmin
   eri nopeudet) */
.js .gka { transform:translate3d(0,52.5vh,0); }
.js .gkb { transform:translate3d(0,82.5vh,0); }
.js .gkc { transform:translate3d(0,65vh,0); }
.js .gkd { transform:translate3d(0,110vh,0); }
/* the receipt: the few facts Anton asked for, in the room grammar below
   the stage. 2026-09-01 (Anton: Grandin ja Julan tekstit kasautuivat
   päällekkäin, ei eroa) the receipt SIGNS ITSELF: a filing-scale name
   under the kicker anchors the facts to Grand before Jula's head
   arrives. The sky keeps the poster name; this is the bookkeeping line,
   clearly below room-head scale. */
.gkuitti { position:relative; z-index:2; padding-top:2vh; padding-bottom:9vh; }
/* #konserni's head scale (3.42vw): the three heads that share the 12vw
   edge once the lights are up (receipt, closing claim, konserni) are one
   family, and the receipt stays clearly below the sky's poster name */
.gknimi { margin:2vh 0 0; font-family:var(--disp); font-weight:500;
  font-size:clamp(29px,3.42vw,50px); letter-spacing:-.025em; line-height:1.05; }
.gknimi .liitos { white-space:nowrap; }
.gknimi .p { display:inline-block; transform:scale(0); transform-box:border-box;
  transform-origin:60% 60%; color:var(--minttu); }
.gknimi.on .p { animation:dotpop .5s cubic-bezier(.3,1.6,.4,1) .55s forwards; }

/* ═══ 3¾ · KUITIN LADONTA JA PIMEÄ KIITORATA (2026-09-04). Tässä oli
   kahden huoneen (Jula, Ogeli) koneisto — .khuone-maa, kulmapaljastus,
   .kseura-kellujat, asemakyltit — kunnes Anton pudotti molemmat kohteet
   etusivulta. Poistettu kokonaan mieluummin kuin jätetty roikkumaan:
   säilytetty on vain se, mitä Grandin kuitti yhä käyttää (mitta, masthead,
   tietorivi, aksentti) ja .haave, joka on nyt näyttämön oma RUSKEASUO.
   MITTA MUUTTUI SAMALLA: pimeä käytävä ajoi 8vw:n reunuksella, koska
   huoneiden kuvat tarvitsivat leveyttä. Yhden kohteen jälkeen kuitti ja
   lopetus ovat ainoat tekstilohkot jäljellä, ja ne nousevat valoon
   #konsernin viereen — sama 12vw kuin muilla paperisivuilla, niin että
   koko sivulla on yksi vasen reuna alusta loppuun. ═══ */
/* no max-width: #konserni, .vastuut and .mitattu all rule a plain 12vw
   and nothing else, and a centred 1560 clamp put this column 20px inside
   theirs — a misalignment that only became visible once the light switch
   started showing the receipt and #konserni on the same paper. */
.khsis { position:relative; padding:0 12vw; }
/* ONE GRAMMAR FOR THE RECEIPT AND THE CLOSING (2026-09-04, Anton: the
   text and the style at both ends did not work). Both are .khsis:
   kicker, a display line on the full measure, then .khrivi — one
   baseline row, serif note left, door hard against the right margin —
   and the fact slots where there are facts. The receipt used to be a
   masthead grid (name left, sentence hanging right) and the closing a
   different row; now the same block closes the chapter it opened. */
.khrivi { position:relative; z-index:2; display:grid;
  grid-template-columns:minmax(0,1fr) auto; gap:2vh 6vw; align-items:baseline;
  margin-top:4.2vh; }
/* ACCENT RULE (Anton 2026-08-11, two rounds: "maybe mint would work" →
   "the color is still off. and the end color is still there"): on paper
   the accent is the sun; in the dark passage it is MUTED sage-mint, and
   NOTHING in the passage is yellow — the first mint was too candy, and
   even "justified" warm light (the threshold viiru, the seam edges) read
   as the unfinished yellow. The seam now leaks the PAPER's own colour.
   Since 2026-09-04 the receipt is inside the light switch, so its accent
   crosses mint → sun on --vt with everything else. */
.khsis .kick { color:color-mix(in oklab, var(--minttu), var(--faded) calc(var(--vt) * 100%)); }
/* the note: serif body, cream in the dark; lit it lands on full ink and
   relaxes to --faded (#konserni's intro grey) only once the ground is
   bright (--vs) — a warm grey on a half-dark ground is unreadable */
.khseloste { font-size:clamp(15px,1.3vw,17.5px); line-height:1.75; max-width:50ch;
  color:color-mix(in oklab, rgba(242,236,221,.74),
    color-mix(in oklab, var(--ink), var(--faded) calc(var(--vs) * 100%))
    calc(var(--vt) * 100%)); }
/* the door: the baseline holds it, no margin of its own. Colour rides the
   frame clock (no .4s ease), so it inverts with the rest of the block. */
.khlinkki { justify-self:end; --nacc:var(--vacc); color:var(--vmuste); }
/* scoped to the front page's receipt (2026-09-01): nordevo.css loads on
   every page and kohteemme names its own archival fact rows .tiedot —
   the bare grid rule smashed them into three overlapping columns */
.gkuitti .tiedot { position:relative; z-index:2; margin-top:6.5vh;
  display:grid;
  grid-template-columns:minmax(0,.92fr) minmax(0,.86fr) minmax(0,1.22fr);
  gap:3vh 5vw; }
.tlabel { font-family:var(--sans); font-weight:500; font-size:10.5px;
  letter-spacing:.2em; text-transform:uppercase;
  color:color-mix(in oklab, rgba(242,236,221,.4), rgba(34,28,18,.5) calc(var(--vt) * 100%)); }
.tarvo { margin-top:1vh; font-family:var(--sans); font-weight:500;
  font-size:clamp(15px,1.35vw,18.5px); letter-spacing:-.015em; line-height:1.4;
  color:color-mix(in oklab, rgba(242,236,221,.92), var(--ink) calc(var(--vt) * 100%)); }
/* the deepest layer on the stage: the place-name where the work stands,
   a station sign drifting behind everything (paraFrame rides it). */
.haave { position:absolute; z-index:1; font-family:var(--disp); font-weight:500;
  font-size:clamp(52px,8.5vw,132px); letter-spacing:-.03em; line-height:1;
  color:color-mix(in oklab, rgba(242,236,221,.07), rgba(34,28,18,.05) calc(var(--vt) * 100%));
  pointer-events:none; user-select:none; white-space:nowrap;
  will-change:transform; }

/* ═══ VALOKATKAISIN. The passage does not cut to paper: the lights come
   up in it. valoFrame writes ONE number, --v (0 = dark ground, 1 =
   paper), per frame onto :root, and every colour is mixed from it in
   oklab — the ground over the full ramp, the type through the narrow
   --vt window (cream, L .94, travelling to ink, L .26, meets a ground
   travelling L .14 → .97 at the same lightness near --v .53: a full-ramp
   blend would float at that dead point, the quick dissolve crosses it in
   a few frames and reads as the type inverting with its ground). Accents
   cross mint → sun in the same window; notes relax to grey on --vs.
   --v, --vt, --vs live on :root so every dark surface still on screen
   (#grand's tail, this stage, the nav) brightens in step. Without JS --v
   holds 0: cream on dark, paper cuts in below.
   VAIHTO ON ÄKILLINEN JA PEHMEÄ (2026-09-04 evening, Anton: "no
   stickiness, just effect that suddenly the colors invert back to light
   smoothly"). No pin, no hold: the page scrolls freely, and once the
   closing block is well on screen the whole picture — the receipt still
   above it, the closing words, the nav — inverts to paper over a SHORT
   stretch of scroll (valoFrame: .30vh of travel, eased, smoothed on the
   frame clock). A first version of this pass held the block on a sticky
   stage for a screen of scroll; Anton killed it the same evening. */
.paatos { position:relative; z-index:3;
  /* SAUMA (2026-09-14, Anton's pick D of the kohteet-2026-09 round): one
     door, no other kohde, no count. Kicker, then one Grand print (the light
     seam in the shower ceiling, shower-seam.jpg, used nowhere else) that
     lies dim on the 12vw measure in the dark and OPENS past its margins to
     the full width as the lights come up, on the switch's own number.
     The ramp clock is the kicker block (.khsis, valoFrame: top .70vh →
     .40vh), so the opening runs while the print is entering. 14vh of air
     below the print keeps the floor rule (r.bottom <= vh snaps --v to 1)
     from firing before the ramp has ended. */
  padding:14vh 0 0; min-height:0;
  background:var(--maa);
  --acc:var(--vacc);
  color:var(--vmuste); }
/* the print: margins 12vw → 0 and height 56vh → 90vh straight from --v;
   the photograph develops from dark duotone to colour on the same number,
   then DARKENS SMOOTHLY (--pimeys, nordevo.js saumaFrame, 2026-09-14,
   Anton: "the image should darken smoothly to make wow effect", then "the
   darken shouldnt be uniform, mostly on the buttons side"): as the opened
   print climbs from .38vh to the top of the screen, the passage's own dark
   comes in FROM THE LEFT, where the door stands (.sauma::after, the ground
   colour in a gradient, its opacity riding --pimeys), while the whole
   frame settles a step (brightness 1 → .78 on the img). The seam of light
   on the right is what remains glowing. The switch's ramp clock is the
   empty .khsis above the figure (the class pads its box 12vw, so it cannot
   ride on the figure itself). */
.sauma { position:relative; z-index:2; margin:5vh calc(12vw * (1 - var(--v))) 14vh;
  height:calc(56vh + 34vh * var(--v)); overflow:hidden; --pimeys:0; }
.sauma .saumakuva { display:block; height:100%; }
.sauma img { display:block; width:100%; height:100%; object-fit:cover; object-position:50% 12%;
  filter:grayscale(calc((1 - var(--v)) * 100%)) sepia(calc((1 - var(--v)) * 40%))
         brightness(calc((.5 + .5 * var(--v)) * (1 - .22 * var(--pimeys))))
         contrast(calc(1 - .06 * (1 - var(--v)))); }
.sauma::after { content:""; position:absolute; inset:0; pointer-events:none;
  opacity:var(--pimeys);
  background:linear-gradient(90deg, rgba(11,9,6,.92) 0%, rgba(11,9,6,.8) 26%, rgba(11,9,6,.5) 50%,
      rgba(11,9,6,.14) 74%, rgba(11,9,6,0) 100%),
    linear-gradient(0deg, rgba(11,9,6,.5) 0%, rgba(11,9,6,0) 45%); }
.no-js .sauma, .static .sauma { margin-left:0; margin-right:0; height:90vh; --pimeys:1; }
.no-js .sauma img, .static .sauma img { filter:brightness(.78); }
/* the door ON the print (Anton: "the cta can be on the image"): the site's
   own button, a size up (Anton: "too small"), in cream because it stands on
   the darkened photograph. It ARRIVES out of the dark (.paatos.auki, saumaFrame sets it
   once --pimeys passes .5): rises a step and fades in, the period stamps
   once landed. Hover is the site's hover. Scrolling back down lets it go.
   Earlier cuts the same day: a hairline pill top-left ("off place and
   lame"), a solid mint pill dead centre ("so off"), a static gradient scrim
   ("not working at all"). */
.saumaovi { position:absolute; z-index:3; left:12vw; top:56%; margin:0;
  transform:translateY(-50%); }
.saumaovi .nappi { --nacc:var(--sun); color:var(--cream); font-size:clamp(24px,2.5vw,38px);
  border-color:color-mix(in oklab, var(--cream), transparent 40%);
  opacity:0; transform:translateY(14px);
  transition:opacity .9s cubic-bezier(.22,1,.36,1), transform .9s cubic-bezier(.22,1,.36,1),
    color .45s ease .05s, border-color .55s ease; }
.saumaovi .nappi .npiste { transform:scale(0); }
.paatos.auki .saumaovi .nappi { opacity:1; transform:none; }
.paatos.auki .saumaovi .nappi .npiste { animation:dotpop .5s cubic-bezier(.3,1.6,.4,1) .55s forwards; }
.paatos.auki .saumaovi .nappi:hover .npiste,
.paatos.auki .saumaovi .nappi:focus-visible .npiste { animation:none; transform:scale(180); }
/* printed: the room is dark and the door stands there */
.no-js .saumaovi .nappi, .static .saumaovi .nappi { opacity:1; transform:none; }
.no-js .saumaovi .nappi .npiste, .static .saumaovi .nappi .npiste { transform:none; animation:none; }

/* ═══ PLATES: the flat print, shared by every page — the window crops the
   photo, the photo pans inside it on its own slower clock ═══ */
.plate { position:relative; z-index:2; will-change:transform; }
.plate .ph { overflow:hidden; }
/* the photo is printed one size larger than its window: 1.12 keeps ±6% of
   its height in reserve, and the pan in nordevo.js spends at most ±5.2%,
   so the frame edge never shows (translate before scale: --dy is true px) */
.plate .ph img { width:100%; height:auto; transform:translateY(var(--dy,0px)) scale(1.12);
  filter:sepia(.45) saturate(.6) contrast(.95) brightness(1.02);
  transition:filter 2.2s ease .2s; will-change:transform; }
.plate.on .ph img { filter:none; }
/* the rise lives on the children: the figure itself belongs to the
   parallax (inline transform), so it can't also carry the reveal */
.js .plate .ph, .js .plate figcaption { opacity:0; transform:translateY(28px); }
.js .plate.on .ph, .js .plate.on figcaption { opacity:1; transform:none;
  transition:opacity 1.35s cubic-bezier(.22,1,.36,1), transform 1.35s cubic-bezier(.22,1,.36,1); }
.js .plate.on figcaption { transition-delay:.15s; }
/* captions read like gallery labels: name, one line of prose, a quiet link */
.plate figcaption { margin-top:18px; display:block; max-width:52ch; }
.plate figcaption b { display:block; font-family:var(--disp); font-size:clamp(19px,1.8vw,25px);
  font-weight:600; letter-spacing:-.02em; }
.plate figcaption b a { text-decoration:none; padding-bottom:2px;
  background:linear-gradient(90deg,var(--sun),var(--sun)) no-repeat 0 100%/0 1px;
  transition:background-size .5s cubic-bezier(.22,1,.36,1); }
.plate figcaption b a:hover { background-size:100% 1px; }
.plate figcaption span { display:block; margin-top:7px; font-size:15.5px; line-height:1.65;
  color:var(--faded); }
/* companion label: one quiet sans line, a receipt more than a caption */
.plate figcaption.pieni { margin-top:12px; font-family:var(--sans); font-size:12.5px;
  letter-spacing:.05em; color:var(--muted); }
.plate figcaption a.myy { display:inline-block; margin-top:10px; font-family:var(--sans);
  font-size:12.5px; letter-spacing:.06em; color:var(--ink); text-decoration:none;
  padding-bottom:2px;
  background:linear-gradient(90deg,var(--sun),var(--sun)) no-repeat 0 100%/100% 1px;
  transition:color .4s ease; }
.plate figcaption a.myy:hover { color:var(--sun); }

/* ═══ 5 · KONSERNI: the register — one screen. Masthead (claim left, one
   short sentence hanging right), then the three companies as compact
   specimen rows, each an entrance to /konserni/ where the full sheet
   lives. The logos keep their own arrivals, scaled down. ═══ */
/* the register's ground is the SAME mix as the passage above it (2026-09-04
   evening, Anton: "i can see the edge on the below page"): --maa is the
   paper exactly at --v 1, and if a flick ever brings this edge up before
   the frame lerp has settled, the two grounds are still one surface */
#konserni { position:relative; z-index:3; background:var(--maa); overflow:hidden; }
#konserni .wrap { padding:9vh 12vw 8vh; position:relative; }
.kyla { display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  gap:2vh 6vw; align-items:end; }
#konserni h2 { margin-top:2.2vh; font-family:var(--disp); font-size:clamp(29px,3.42vw,50px);
  font-weight:500; letter-spacing:-.025em; }
#konserni .intro { justify-self:end; font-size:clamp(14.5px,1.2vw,16.5px); line-height:1.72;
  color:var(--faded); max-width:44ch; padding-bottom:.6vh; }
/* compact specimen rows: hairline rules carry an ember at their left end;
   the whole row is a link into the konserni sheet. */
.rivit { margin-top:3.6vh; }
.rivi { position:relative; display:grid;
  grid-template-columns:minmax(220px,320px) minmax(0,1fr) auto;
  gap:2vh 5vw; align-items:center; padding:3vh 0;
  color:inherit; text-decoration:none; }
.rivi::before, .rivi:last-child::after { content:""; position:absolute; left:0; right:0;
  height:1px; background:linear-gradient(90deg,
    rgba(194,90,23,.85), rgba(34,28,18,.30) 26%, rgba(34,28,18,.13) 100%);
  transform:scaleX(0); transform-origin:left;
  transition:transform 1.1s cubic-bezier(.22,1,.36,1); }
.rivi::before { top:0; }
.rivi:last-child::after { bottom:0; transition-delay:.3s; }
.rivi.on::before, .rivi:last-child.on::after { transform:none; }
.rlogo { display:block; color:var(--ink); min-width:0; }
.yseloste { display:block; font-size:clamp(14px,1.15vw,16px); line-height:1.66;
  color:var(--faded); max-width:46ch; transition:color .4s ease; }
.rivi:hover .yseloste { color:var(--ink); }
.rmene { color:var(--muted); transition:color .4s ease; }
.rmene .nuoli { margin-left:0; }
.rivi:hover .rmene { color:var(--ink); }
.js .yseloste { opacity:0; transform:translateY(12px); }
.js .rivi.on .yseloste { opacity:1; transform:none;
  transition:opacity 1s cubic-bezier(.22,1,.36,1) 1.05s,
             transform 1s cubic-bezier(.22,1,.36,1) 1.05s,
             color .4s ease; }
/* Asunnot: the parent wordmark settles, ASUNNOT tracks in, the period stamps warm */
.riviA .alogo { --dot:var(--piste); display:block; width:min(100%,280px); height:auto; }
.js .riviA .alogo { --dot:var(--paper); opacity:0; transform:translateY(16px); }
.js .riviA.on .alogo { --dot:var(--piste); opacity:1; transform:none;
  transition:opacity 1.1s cubic-bezier(.22,1,.36,1) .2s,
             transform 1.1s cubic-bezier(.22,1,.36,1) .2s,
             --dot .7s ease 1.5s; }
.asub { display:block; width:calc(min(100%,280px) + .5em); margin-top:1vh;
  font-family:var(--sans); font-size:clamp(10.5px,.9vw,13px); font-weight:500;
  text-transform:uppercase; text-align:right; letter-spacing:.5em; color:var(--ink); }
.js .asub { opacity:0; letter-spacing:.85em; }
.js .riviA.on .asub { opacity:1; letter-spacing:.5em;
  transition:opacity .9s ease .8s, letter-spacing 1.4s cubic-bezier(.22,1,.36,1) .8s; }
/* Nordewood: the three timber bars rise from the baseline, the name prints */
.riviW .wlogo { display:block; width:min(100%,316px); height:auto; }
.js .riviW .palkki { transform-box:fill-box; transform-origin:50% 100%;
  transform:scaleY(0); transition:transform .9s cubic-bezier(.22,1,.36,1) .25s; }
.js .riviW .palkki.puu { transition-delay:.45s; }
.js .riviW .palkki:nth-child(3) { transition-delay:.65s; }
.js .riviW.on .palkki { transform:none; }
.js .riviW .nsana { opacity:0; transition:opacity 1.2s ease 1s; }
.js .riviW.on .nsana { opacity:1; }
/* HNL: the monoline mark draws itself stroke by stroke (house, H bar, N stem, N diagonal, L foot), smile last */
.riviH .rlogo { display:flex; align-items:flex-end; gap:clamp(14px,1.5vw,24px); }
.hmerkki { display:block; height:clamp(58px,5.4vw,84px); width:auto; flex:none; }
.js .hmerkki .hv { stroke-dasharray:1; stroke-dashoffset:1; }
.js .riviH.on .hv { stroke-dashoffset:0; }
.js .riviH.on .h1 { transition:stroke-dashoffset 1.05s cubic-bezier(.45,.1,.3,.9) .15s; }
.js .riviH.on .h2 { transition:stroke-dashoffset .24s ease-out 1.1s; }
.js .riviH.on .h3 { transition:stroke-dashoffset .38s ease-out 1.3s; }
.js .riviH.on .h4 { transition:stroke-dashoffset .38s ease-out 1.62s; }
.js .riviH.on .h5 { transition:stroke-dashoffset .28s ease-out 1.94s; }
.js .riviH.on .h6 { transition:stroke-dashoffset .7s cubic-bezier(.3,.6,.3,1) 2.2s; }
.hnimi { display:flex; flex-direction:column; gap:.55em; font-family:var(--disp);
  font-size:clamp(10px,.85vw,12.5px); font-weight:400; letter-spacing:.32em;
  line-height:1; color:var(--ink); padding-bottom:4px; }
.hnimi i { font-style:normal; }
.js .hnimi i { opacity:0; transform:translateY(10px); }
.js .riviH.on .hnimi i { opacity:1; transform:none;
  transition:opacity .8s ease, transform .8s cubic-bezier(.22,1,.36,1); }
.js .riviH.on .hnimi i:nth-child(1) { transition-delay:1.15s; }
.js .riviH.on .hnimi i:nth-child(2) { transition-delay:1.33s; }
.js .riviH.on .hnimi i:nth-child(3) { transition-delay:1.51s; }
/* the way into the full sheet */
#konserni .kmene { margin-top:4vh; }
#konserni .kmene a { color:var(--ink); }

/* ═══ 6 · TEKIJÄT ═══ */
#tekijat { padding:16vh 12vw 12vh; }
#tekijat h2 { margin-top:2.5vh; font-family:var(--disp); font-size:clamp(29px,3.42vw,50px);
  font-weight:500; letter-spacing:-.025em; }
#tekijat .intro { margin-top:3vh; font-size:clamp(15.5px,1.35vw,18px); line-height:1.75;
  color:var(--faded); max-width:52ch; }
.hlot { display:grid; grid-template-columns:repeat(3,1fr); gap:8vh 4vw; margin-top:9vh; }
.hlo .nimi { font-family:var(--disp); font-size:clamp(20px,1.8vw,26px); font-weight:600;
  letter-spacing:-.02em; width:fit-content; padding-bottom:3px;
  background:linear-gradient(90deg,var(--sun),var(--sun)) no-repeat 0 100%/0 2px;
  transition:background-size .55s cubic-bezier(.22,1,.36,1); }
.hlo:hover .nimi { background-size:100% 2px; }
.hlo .rooli { margin-top:7px; font-family:var(--sans); font-size:11px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--muted); }
.hlo .fakta { margin-top:10px; font-size:15px; line-height:1.6; color:var(--faded); max-width:30ch; }
/* nine laths: the team as a timber facade — hover a board and it opens.
   Greyscale at rest, the person develops to color as their board widens. */
.laudat { display:flex; gap:7px; height:min(62vh,560px); margin-top:8vh; }
.lauta { position:relative; flex:1 1 0; min-width:0; overflow:hidden; text-decoration:none;
  transition:flex-grow .9s cubic-bezier(.22,1,.36,1); }
.lauta::before { content:""; position:absolute; inset:0;
  background:var(--pf) center 16%/cover no-repeat;
  filter:grayscale(.92) contrast(.96) brightness(1.03); transform:scale(1.02);
  transition:filter .9s ease, transform .9s cubic-bezier(.22,1,.36,1); }
.lauta::after { content:""; position:absolute; left:0; right:0; bottom:0; height:46%;
  background:linear-gradient(0deg, rgba(15,12,8,.66), rgba(15,12,8,0)); opacity:0;
  transition:opacity .6s ease; }
.lauta:hover, .lauta:focus-visible { flex-grow:3.4; }
.lauta:hover::before, .lauta:focus-visible::before { filter:none; transform:scale(1); }
.lauta:hover::after, .lauta:focus-visible::after { opacity:1; }
.lteksti { position:absolute; left:14px; right:10px; bottom:13px; z-index:2;
  color:#f6efe0; opacity:0; transform:translateY(8px);
  transition:opacity .55s ease .12s, transform .55s cubic-bezier(.22,1,.36,1) .12s; }
.lauta:hover .lteksti, .lauta:focus-visible .lteksti { opacity:1; transform:none; }
.lnimi { display:block; font-family:var(--disp); font-weight:600;
  font-size:clamp(15px,1.5vw,21px); letter-spacing:-.015em; white-space:nowrap; }
.lrooli { display:block; margin-top:3px; font-family:var(--sans); font-size:10.5px;
  letter-spacing:.14em; text-transform:uppercase; color:rgba(246,239,224,.78); }
.fine { margin-top:10vh; font-size:15px; color:var(--muted); }
#tekijat .fine { margin-top:9vh; }
.tlink { margin-top:2.5vh; }
#tekijat .tlink { margin-top:7vh; }
.tlink a { color:var(--ink); }

/* ═══ 7 · LOPPU: the footer lives in loppu.css since 2026-09-15 (the slab,
   the dark room, the ticker, the panel). The doors and the subpage .yht
   register are gone. ═══ */

/* ═══ responsive ═══ */
@media (max-width:860px) {
  html { scroll-padding-top:60px; }
  .nav { min-height:52px; padding:16px 18px 12px; }
  .nav ul { display:none; }
  .nav .menuBtn { display:block; font-family:var(--sans); font-size:11.5px; letter-spacing:.18em;
    text-transform:uppercase; color:var(--faded); background:none; border:0; cursor:pointer; padding:6px; }
  .nav.inv .menuBtn { color:color-mix(in oklab, rgba(242,236,221,.9), var(--faded) calc(var(--nav-type-light) * 100%)); }
  .cmark { width:80vw; }
  .saapuminen { left:7vw; bottom:9vh; }
  #sanat { padding:13vh 7vw 10vh; }
  .sana { font-size:clamp(40px,10.5vw,64px); }
  .sana:nth-child(2), .sana:nth-child(3), .sana:nth-child(4),
  .sana:nth-child(5), .sana:nth-child(6), .sana:nth-child(7),
  .sana:nth-child(8) { margin-left:0; }
  .sana .selite { display:block; margin:4px 0 0; vertical-align:baseline; }
  /* the type room stacks: claim, lede, photo, promises and numbers in
     single file, all on the same 7vw margin. The lede keeps its own
     measure under the claim rather than becoming a second column. */
  #miksi { padding:0 0 11vh; }
  .mikkuna { padding-top:13vh; }
  .skatto { margin:8vh -3.5vw 0; }
  .skatto img { height:52vh; }
  .mhead { grid-template-columns:1fr; gap:3.4vh 0; padding:0 7vw; }
  .miso { font-size:clamp(38px,11.5vw,70px); }
  .mlede { padding-bottom:0; }
  .mlede p { max-width:38ch; }
  .mtodiste { width:100%; margin:8vh 0 0; padding:0 7vw; }
  .plate.mtodiste .ph img { height:auto; }
  .vastuut { margin-top:10vh; padding:0 7vw; }
  .vast { grid-template-columns:1fr; gap:1.8vh 0; padding:6vh 0 6.5vh; }
  .vast h3 { max-width:none; font-size:clamp(36px,12vw,62px); }
  .vteksti { padding-top:0; }
  .mitattu { margin-top:11vh; padding:0 7vw; }
  .luvut { grid-template-columns:1fr 1fr; gap:5vh 6vw; margin-top:5vh; }
  .luku .iso { font-size:clamp(36px,11vw,64px); }
  .mitattu .fine { margin-top:5vh; }
  /* the stage ghost stays off: its 196vh spot lands on the mobile receipt */
  .haave { display:none; }
  .gnayttamo { padding-bottom:60vh; }
  .gtausta { height:135vh; }
  .gnimi { top:10vh; left:7vw; font-size:clamp(27px,8.6vw,50px); white-space:normal; }
  /* two lit rooms only, arriving after the name has read. The FASTER one
     (gkb) leads, so the gap between them grows while they float instead
     of closing; speeds soften on mobile (grandFrame mob 0.7) and the
     enter-states scale to match. */
  .gkc, .gkd { display:none; }
  .gkb { top:128vh; left:auto; right:5vw; width:52vw; }
  .gka { top:177vh; right:auto; left:5vw; width:48vw; }
  .js .gkb { transform:translate3d(0,57.8vh,0); }
  .js .gka { transform:translate3d(0,36.8vh,0); }
  .gkuitti { padding-bottom:6vh; }
  .gknimi { font-size:clamp(22px,6.5vw,32px); }
  .khsis { padding:0 7vw; }
  /* stacked: the door drops under the note and returns to the left
     margin — a 7vw column has no right edge worth reaching for */
  .khrivi { grid-template-columns:1fr; gap:2.6vh 0; margin-top:3.6vh; }
  .khlinkki { justify-self:start; }
  .khseloste { max-width:none; }
  .gkuitti .tiedot { grid-template-columns:1fr; gap:2.8vh; margin-top:5vh; }
  #grand { --kiito:8vh; }
  .paatos { padding:12vh 0 0; }
  .sauma { margin:4vh calc(7vw * (1 - var(--v))) 12vh; height:calc(50vh + 24vh * var(--v)); }
  .no-js .sauma, .static .sauma { height:74vh; }
  .saumaovi { left:7vw; top:56%; }
  .saumaovi .nappi { font-size:clamp(20px,5.6vw,24px); }
  #konserni .wrap { padding:11vh 7vw 9vh; }
  .kyla { grid-template-columns:1fr; }
  #konserni .intro { justify-self:start; margin-top:2.4vh; padding-bottom:0; }
  .rivit { margin-top:3vh; }
  .rivi { grid-template-columns:minmax(0,1fr) auto; gap:2.4vh 6vw; padding:4vh 0;
    align-items:end; }
  .rivi .rlogo { grid-column:1 / -1; }
  .riviA .alogo { width:min(100%,250px); }
  .asub { width:calc(min(100%,250px) + .5em); font-size:clamp(10.5px,2.6vw,12.5px); }
  .riviW .wlogo { width:min(100%,280px); }
  .riviH .rlogo { gap:clamp(12px,3.4vw,20px); }
  .hmerkki { height:clamp(52px,13vw,66px); }
  .hnimi { font-size:clamp(9.5px,2.4vw,11px); }
  .yseloste { max-width:none; }
  .yseloste { padding-bottom:0; }
  #tekijat { padding-left:7vw; padding-right:7vw; }
  .hlot { grid-template-columns:1fr 1fr; gap:6vh 6vw; }
  /* laths become a portrait grid on small screens: full color, names printed */
  .laudat { height:auto; display:grid; grid-template-columns:repeat(3,1fr); gap:8px; }
  .lauta { height:40vw; }
  .lauta::before { filter:none; }
  .lauta::after { opacity:1; }
  .lteksti { opacity:1; transform:none; left:10px; right:8px; bottom:9px; }
  .lnimi { font-size:13px; white-space:normal; line-height:1.2; }
  .lrooli { font-size:9px; letter-spacing:.1em; }
}
@media (max-width:520px) {
  .hlot { grid-template-columns:1fr; }
  .luvut { grid-template-columns:1fr 1fr; }
  .lauta { height:46vw; }
  .lnimi { font-size:11.5px; }
  .lrooli { display:none; }
}

/* ═══ no-js / reduced motion ═══ */
.no-js .oikkuna, .static .oikkuna { position:relative; height:92vh; -webkit-mask-image:none; mask-image:none; }
.no-js #hero, .static #hero { height:auto; }
/* the still hero is two screens in a column, the mark and then the room it
   opens into: the pinned 100vh box would cut the room to a strip, and the
   dark print belongs to the first screen only */
.no-js #hero .hold, .static #hero .hold { position:relative; height:auto; }
.no-js .yo, .static .yo { bottom:auto; height:92vh; }
.no-js .masthead, .static .masthead { position:relative; height:92vh; }
.no-js .plate .ph img, .static .plate .ph img { filter:none; transform:none; }
.static .plate .ph, .static .plate figcaption { opacity:1; transform:none; transition:none; }
/* grand without motion: the house rests half-darkened so the name reads,
   and the floaters rest at their base spots */
.no-js .gtausta img, .static .gtausta img { filter:brightness(.5); }
.no-js .gkelluja, .static .gkelluja { transform:none; }
/* at rest the last room ends below the stage's tail (in motion it has
   floated up by then) and lay over the receipt: the still stage runs on
   until that room has ended. Last room's top, its height (3:4 of its
   width), a breath of air, less the 185vh photograph. */
.no-js .gnayttamo, .static .gnayttamo { padding-bottom:calc(49vh + min(30.7vw,440px)); }
@media (max-width:860px) {   /* two rooms on a phone, the last one 48vw wide from 177vh on a 135vh photograph */
  .no-js .gnayttamo, .static .gnayttamo { padding-bottom:calc(48vh + 64vw); }
}
.no-js .gnimi .p, .static .gnimi .p { transform:none; animation:none; }
.no-js .lr, .static .lr { opacity:1; }
.no-js .pd0, .static .pd0 { transform:none; }
.no-js .halo, .static .halo { display:none; }
.no-js .vihje, .static .vihje { display:none; }
.no-js .varjo, .static .varjo { opacity:1; }
.no-js .saapuminen, .static .saapuminen { opacity:1; transform:none; }
.no-js .sana, .static .sana { color:var(--ink); }
.no-js .sana .p, .static .sana .p { transform:none; }
.no-js #sanat .jalki .p, .static #sanat .jalki .p { transform:none; animation:none; }
.no-js .sana .selite, .static .sana .selite { opacity:1; }
.no-js .miso .p, .static .miso .p { transform:none; animation:none; }
.no-js .lauta::before, .static .lauta::before { filter:none; }
.no-js .lauta::after, .static .lauta::after { opacity:1; }
.no-js .lteksti, .static .lteksti { opacity:1; transform:none; }
.no-js .viiru, .static .viiru { opacity:.38; transform:none; }
.no-js .gknimi .p, .static .gknimi .p { transform:none; animation:none; }
/* the sentence already ended: the full stop stands stamped, its light calm */
/* nothing drives --v without the scroll loop, so it holds its default:
   the kicker stays cream on dark, the print is printed open and in colour
   (see .no-js .sauma above) and the paper below cuts in. The register
   paints its ground from --maa for the switch's sake, which without the
   switch is the passage's dark under ink type: it is paper here. */
.no-js #konserni, .static #konserni { background:var(--paper); }

.no-js .rivi::before, .static .rivi::before,
.no-js .rivi:last-child::after, .static .rivi:last-child::after { transform:none; transition:none; }
.static .riviA .alogo, .static .asub, .static .yseloste, .static .riviW .palkki,
.static .riviW .nsana, .static .riviH .hv, .static .hnimi i { transition:none; }
.static .riviA .alogo { --dot:var(--piste); }
.static .nav, .no-js .nav { opacity:1; transform:none; pointer-events:auto; }
.no-js .nav { --nav-paper:var(--paper); }

/* Functional text uses ink; the accent stays the light and the punctuation. */
.menu[hidden] { display:none; }
.menu { overflow-y:auto; min-height:0; padding:70px 7vw; }
.menu a { flex-shrink:0; }
:focus-visible { outline:2px solid currentColor; outline-offset:5px; }
::selection { background:var(--sun); color:var(--paper); }
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0; }
.quicklinks { display:flex; flex-wrap:wrap; gap:8px 24px; margin-top:24px;
  font:500 14px/1.5 var(--sans); }
.quicklinks a { padding:8px 0; text-decoration-thickness:1px; text-underline-offset:6px;
  text-decoration-color:var(--muted); }
.quicklinks a:hover { text-decoration-color:currentColor; }
.mail-copy { margin-top:20px; font:16px/1.5 var(--sans); color:var(--faded); }
.mail-copy summary { cursor:pointer; padding:8px 0; }
.mail-copy p { margin:12px 0; }
.mail-copy textarea { display:block; width:100%; padding:12px; font:16px/1.5 var(--sans);
  color:var(--ink); background:var(--paper); border:1px solid var(--muted); }
@media (max-width:860px) {
  .no-js .nav { position:relative; top:0; left:0; right:0; border-radius:0; opacity:1; background:var(--paper); color:var(--ink); flex-wrap:wrap; }
  .no-js .nav ul { display:flex; flex-wrap:wrap; gap:8px 20px; padding-top:16px; width:100%; }
  .no-js .nav ul a { color:var(--ink); padding:8px 0; }
  .no-js .nav .menuBtn { display:none; }
}
