/* Kapta.fi site styles. The Customizer Lisä-CSS field must stay empty. */

/* ===== THEME SHEET ===== */

/* ===== VARIABLES ===== */

:root {

	/* Structure widths — FULL-WIDTH MODE (2026-08-27): the frame spans the
	   viewport; a fluid gutter replaces the centered 1535px column */

	--max-width: 100%;
	--max-width-static: 100%;
	--page-gutter: clamp(1.25rem, 3vw, 3.5rem);

	/* Sizes — mapped onto the DS type scale (tokens.css) */

	--size-h1: var(--text-3xl);
	--size-h2: var(--text-2xl);
	--size-h3: var(--text-xl);
	--size-h4: var(--text-base);
	--size-h5: var(--text-base);
	--size-p: var(--text-base);
	--size-p-small: var(--text-sm);
	--size-p-x-small: var(--text-xs);

	/* Margins — DS space scale; large layout steps derive from --space-section */

	--margin-small: var(--space-3);
	--margin-medium: var(--space-5);
	--margin-large: calc(var(--space-section) / 2);
	--margin-x-large: calc(var(--space-section) * 0.75);
	--margin-xx-large: var(--space-section);
	
	/* Radiuses */
	
	--radius-small:0.5rem;
	--radius-medium:1rem;
	--radius-large:2rem;
	--radius-x-large:4rem;
	--radius-xx-large:6rem;

	/* Colors */
	
	--color-bg:#fafafa;
	--color-primary: #effefc;
	
	--color-black:#050505;
	--color-white:#fefefe;
	--color-gray:#a8a8a8;
	--color-light-gray:#f0f0f0;
	--color-dark-gray:#292929;
	--color-ash:#fafafa;
	--color-blue:#5296d5;
	--color-pink:#f6ebe9;
	--color-dark-green:#004d3b;
	--color-orange:#e69f3b;
	--color-red:#ba1717;
	
	/* Font families — all roles resolve to the DS Archivo tokens */

	--sans-serif: var(--font-body);
	--heading: var(--font-display);
	--heading-thin: var(--font-display);
	--heading-expanded: var(--font-display);

	/* Transitions */

	--transition: var(--motion-medium) ease;
	
}

/* === MAIN STYLING === */

body {
  overflow-x: hidden;
}

/* Hide WordPress title */

.page .entry-header {
	display: none;
}

#wpadminbar {
	display:none;
}

html {
	margin-top:0 !important;
}

/* Typography */

/* h1–h3 = DS title role; h4 = DS label (h6 spec: subtitle face, uppercase, tracked) */

h1 {
 	font-family: var(--heading);
	font-weight: var(--title-weight);
	font-stretch: var(--title-stretch);
	font-size: var(--size-h1);
	line-height:1.1;
	margin-top:0;
}


h2 {
	font-family: var(--heading);
	font-weight: var(--title-weight);
	font-stretch: var(--title-stretch);
	font-size: var(--size-h2);
	line-height:1.15;
}

h3 {
	font-family: var(--heading);
  text-decoration: none;
  font-size: var(--size-h3);
	font-weight: var(--title-weight);
	font-stretch: var(--title-stretch);
	line-height:1.2;
}

h4 {
	font-family: var(--heading);
  text-decoration: none;
  font-size: var(--size-h4);
	text-transform:uppercase;
	font-weight: var(--eyebrow-weight);
	font-stretch: var(--eyebrow-stretch);
	letter-spacing: var(--tracking-label);
}

p {
	font-family: var(--sans-serif);
  text-decoration: none;
	font-size: var(--text-base);
	line-height: var(--leading-normal);
}

li {
	font-family: var(--sans-serif);
  text-decoration: none;
	font-size: var(--text-base);
	line-height: var(--leading-normal);
}

/* .qc-quote joins .statement in the exclusion list: the 1.2em !important
   below would otherwise beat any size a component sets on its own quotes */
blockquote:not(.statement, .qc-quote), blockquote:not(.statement, .qc-quote) p {
		font-family: var(--heading-expanded);
    font-size: 1.2em !important;
    line-height: 1.5em;
	font-weight: var(--subtitle-weight);
	font-stretch: var(--subtitle-stretch);
    color: #111;
    margin-top: 1.5em;
    margin-bottom: 1.5em;
    margin-left: 2em;
    margin-right: 2.5em
}

/* Lists */

ul {
	margin-left:0;
	padding-left:0;
}

li {
	list-style-type:none;
}
	
/* Interaction states — DS 2.4: one universal ring on :focus-visible only.
   a:focus-visible repeated to out-cascade style.css's legacy a:focus dotted outline. */

:focus-visible,
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
	outline: var(--focus-width) solid var(--color-focus);
	outline-offset: var(--focus-offset);
}

:focus:not(:focus-visible) {
	outline: none;
}

::selection {
	background: var(--color-selection);
	color: var(--mono-1);
}

/* Links — DS rule: visited is identical to unvisited, so NO :visited selectors
   anywhere (a bare a:visited outranks component classes like .btn-black and
   paints their text black). Plain a/component rules cover visited links. */

a, a:hover {
	color:var(--color-black);
}

/* Basic link — DS scheme: accent rest, accent-hover on hover, accent-active
   pressed, --motion-fast. Components styling their own links stay excluded. */

a:not(.acc-set a, a.btn, a.btn-centered, #lightbox-content a, #masthead a, .tags a, footer a, .knav a, .statement-cite a, .link-pill, #menu a, .heading-h4 a) {
	color: var(--color-accent);
	transition: color var(--motion-fast) ease;
	text-decoration: none;
}

a:hover:not(.acc-set a:hover, a.btn, a.btn-centered, #lightbox-content a, #masthead a, .tags a, footer a, .knav a, .statement-cite a, .link-pill, #menu a, .heading-h4 a) {
	color: var(--color-accent-hover);
}

a:active:not(.acc-set a, a.btn, a.btn-centered, #lightbox-content a, #masthead a, .tags a, footer a, .knav a, .statement-cite a, .link-pill, #menu a, .heading-h4 a) {
	color: var(--color-accent-active);
}

/* Eyebrow links (2026-08-31): the h4 eyebrow on every service page links to
   its hub (pillar → hub → service tree) and must READ as the eyebrow it
   always was — ink at rest, the standard accent only on hover. Hence the
   exclusion above: the global accent-at-rest link rule would have painted
   every eyebrow green and broken the heading rhythm. */
.heading-h4 a {
	color: inherit;
	text-decoration: none;
	transition: color var(--motion-fast) ease;
}
.heading-h4 a:hover { color: var(--color-accent); }
/* Tag link */

.tags a {
	color: var(--color-black);
	background:var(--color-ash);
	transition: color var(--motion-fast) ease, border-color var(--motion-fast) ease;
	text-decoration: none;
	border:1px solid #fff;
}

.tags a:hover {
	color: var(--color-accent);
	border:1px solid var(--color-accent);
}
/* The footer's own link style now lives with the footer card (see "Footer
   card" below): the DS UI-element rule, the same one the menu curtain uses.
   The white gradient-sweep that used to live here was a third underline
   idiom for one element, and it drew itself on a ground the card no longer
   has at rest. */

/* Tag links */

.meta a {
	margin-right: 10px;
	margin-bottom: 10px;
  padding: 10px 20px !important;
  display: inline-block;
  border-radius:var(--radius-small);
	font-size:var(--size-p-x-small);
}

/* SVG */

svg.outbound-link {
  width: 1em;
  display: inline;
  margin-right: 50px;
  position: relative;
  top: 0.15em;
}


/* ===== SITE ===== */

/* ===== Structure ===== */

body {
	overflow-x: hidden;
	background:var(--color-bg);
}

.row {
	display:flex;
	gap:0;
}

.row-fw {
	display:flex;
	flex-wrap:wrap;
	gap:10%;
}

.row-mw {
	max-width:90vw;
	margin:auto;
}

.col-25 {
	flex: 0 0 20%;
}

.col-50 {
	flex: 0 0 45%;
}

.col-75 {
	flex: 0 0 70%;
}

.col-100 {
	flex:0 0 auto !important;
	width:95%;
	margin:0 auto !important;
	padding-right:2.5%;
	padding-left:2.5%;
}

.wp-block-columns {
	margin-bottom:0;
}

figure img {
	border-radius:1rem;
	width:75vw;
}

.m-r-medium {
	margin-right:5rem !important;
}

.centered {
	text-align:center;
}

/* ===== Page ===== */

#page {
	display:flex;
	justify-content:center;
	flex-wrap:wrap;
	margin: 0;
  border-radius: 0.5rem;
	z-index:5;
	max-width:var(--max-width);
	width:var(--max-width-static);
	margin:0 auto;
	padding: 0 var(--page-gutter);
	box-sizing: border-box;
}

.page {
	display:flex;
	flex-direction:column;
	justify-content: center;
	margin-bottom:0;
}

/* Masthead: see css/nav.css */

@keyframes fadeInUp {
  0% {
    opacity: 0;
    transform: translateY(20px); 
  }
	50% {
    opacity: 0; 
    transform: translateY(20px);
 		 }
  100% {
    opacity: 1;
    transform: translateY(0); 
  }
}

/* Hamburger — flex child of .knav-row, sits next to the CTA pill and scales
   to it (icon 22×16 inside a ~38×32 hit area ≈ pill height). margin-left:auto
   keeps it at the row's right edge when items + CTA hide ≤1400px. */

#navigation {
		display: flex;
		align-items: center;
		margin-left: auto;
}

/* 44×44 (2026-08-30, was 38×32). This is the ONLY way to open the navigation
   below --bp-nav, so it takes the Apple/Material 44px figure rather than the
   WCAG 2.5.8 floor of 24px, which the old box already cleared. The GLYPH is
   unchanged — the same 22px bars, re-centred in the bigger box (left 8→11,
   tops 8/15/22 → 15/22/29). Only the hit area grew. */
#menuToggle {
			display: block;
			position: relative;
			width: 44px;
			height: 44px;
			-webkit-user-select: none;
			user-select: none;
}

#menuToggle input {
			display: block;
			position: absolute;
			inset: 0;
			width: 100%;
			height: 100%;
			margin: 0;
			cursor: pointer;
			opacity: 0;
			z-index: 100;
}

/* Bars: absolute inside the 44×44 box; the X forms by the outer bars sliding
   to the middle bar's line and rotating about their own centers — centered by
   construction, no translate nudges. */
#menuToggle span {
			display: block;
			position: absolute;
			left: 11px;
			width: 22px;
			height: 2px;
			background: var(--mono-1);
			border-radius: 2px;
			z-index: 99;
			transform-origin: 50% 50%;
			transition: transform 0.5s cubic-bezier(0.77,0.2,0.05,1.0),
			top 0.5s cubic-bezier(0.77,0.2,0.05,1.0),
			opacity 0.4s ease;
}

#menuToggle span:nth-of-type(1) { top: 15px; }
#menuToggle span:nth-of-type(2) { top: 22px; }
#menuToggle span:nth-of-type(3) { top: 29px; }

#menuToggle input:checked ~ span:nth-of-type(1) {
			top: 22px;
			transform: rotate(45deg);
}

#menuToggle input:checked ~ span:nth-of-type(2) {
			opacity: 0;
			transform: scaleX(0.2);
}

#menuToggle input:checked ~ span:nth-of-type(3) {
			top: 22px;
			transform: rotate(-45deg);
}

/* Fullscreen overlay. Lives OUTSIDE .knav in the DOM (a fixed element inside
   the glassmorphism bar breaks on mobile WebKit — backdrop-filter ancestors
   mangle fixed descendants), so position:fixed is plain viewport coordinates.
   The bar's checkbox drives it via body:has(). z-index 89 keeps it just under
   the bar (--z-nav 90) so the toggle stays visible and clickable to close. */
#menu {
  position: fixed;
  z-index: 89;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  display: flex;
  /* start, not center: the overlay carries the service groups now and can be
     taller than the screen once one is open. Centred content in a scroller
     clips at the TOP, which is unreachable. */
  align-items: flex-start;
  /* links sit on the page's left edge — the same gutter the nav logo uses */
  justify-content: flex-start;
  padding-left: var(--page-gutter, 3rem);
  padding-right: var(--page-gutter, 3rem);
  /* clear the fixed bar, and leave a floor so the last item is not flush */
  padding-top: calc(var(--knav-h, 4.75rem) + var(--space-5));
  padding-bottom: var(--space-6);
  overflow-y: auto;
  /* the page behind must not scroll when the overlay's own scroll ends */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  /* the slats ARE the background now; the panel itself is only a frame.
     visibility (not opacity) so a closed overlay cannot swallow clicks —
     held until the slats have finished retracting. */
  background: none;
  visibility: hidden;
  transition: visibility 0s 0.78s;   /* held until the curtain has retracted */
}

body:has(#menuToggle input:checked) #menu {
  visibility: visible;
  transition: visibility 0s 0s;
}

/* The curtain: paper-slat language from the hero and the card stack, turned
   dark and dropped from above. Opening sweeps left → right; closing reverses
   — each state carries its own per-slat delay, so the direction flips with
   the direction of travel rather than replaying the same order backwards. */
.menu-slats {
  position: absolute;
  inset: 0;
  display: flex;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

.menu-slats i {
  flex: 1 1 0;
  background: var(--mono-1);
  transform: translateY(-101%);
  transition: transform var(--motion-slow, 0.28s) var(--ease-standard, ease);
  /* closing: right → left, and only AFTER the links have gone (0.15s). The
     two used to overlap, so half-faded links hung over a half-open curtain
     with the page showing through the gaps behind them. */
  transition-delay: calc(0.15s + (7 - var(--i)) * 0.045s);
}

/* overlap by a pixel — flex sub-pixel rounding opens seams otherwise
   (learned on the stack's slats) */
.menu-slats i + i { margin-left: -1px; }

body:has(#menuToggle input:checked) .menu-slats i {
  transform: translateY(0);
  transition-delay: calc(var(--i) * 0.045s);         /* opening: left → right */
}

body:has(#menuToggle input:checked) #menu {
  transform: translate(0, 0);
  opacity: 1;
  transition: opacity 0.5s ease-in-out, transform 0s;
}

/* the list rides above the curtain */
#menu .menu-primary-menu-container {
  position: relative;
  z-index: 1;
}

/* ---- Overlay contents (the site's whole nav below --bp-nav) ------------- */

.menu-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 44rem;
}

/* Brand mark above the menu content (2026-08-31). The MARK, not the
   wordmark: the masthead's wordmark already inverts to white and stays
   visible over the open overlay, so the wordmark here would double it.
   Not a link — the redundant home link was removed on purpose; the mark is
   presence, not navigation. Joins the staged arrival just ahead of the
   groups. */
.menu-brand { margin: 0 0 var(--space-4); }
.menu-brand img { display: block; width: 3.5rem; height: auto; }
.menu-brand {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.15s ease, transform 0.15s ease;
}
body:has(#menuToggle input:checked) .menu-brand {
  opacity: 1;
  transform: translateY(0);
  transition-duration: 0.3s;
  transition-delay: 0.58s;
}

/* Service groups. The toggle matches the top-level links' voice one step
   down, so the overlay reads as one list with sections rather than as two
   different menus stacked. */
.menu-group { margin: var(--space-3) 0 0; }

.menu-group-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  /* WCAG 2.5.8 wants 24px; this is a primary control on a touch screen, so it
     takes the 44px Apple/Material figure instead */
  min-height: 44px;
  padding: var(--space-2) 0;
  border: 0;
  background: none;
  color: var(--mono-6);
  font-family: var(--font-display);
  font-weight: var(--subtitle-weight);
  font-stretch: var(--subtitle-stretch);
  font-size: var(--text-2xl);
  line-height: var(--leading-snug);
  text-align: left;
  cursor: pointer;
}

.menu-chev {
  display: block;
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  transition: transform var(--motion-slow, 0.28s) var(--ease-standard, ease);
}
.menu-chev svg { display: block; width: 100%; height: 100%; }
.menu-group-toggle[aria-expanded="true"] .menu-chev { transform: rotate(180deg); }

/* `hidden` does the work with no JS; the attribute is toggled by nav.js.
   display:none rather than height animation — an accordion that animates its
   height has to measure content that is itself still settling, and this list
   is long enough that the cost is real for no gain a reader would notice. */
.menu-group-panel[hidden] { display: none; }
.menu-group-panel { padding: var(--space-2) 0 var(--space-4); }

/* The megamenu panel markup, restacked. Same HTML the desktop panel uses —
   only the axis changes, so nothing here is a second copy of the service list. */
#menu .knav-cols { display: block; }
#menu .knav-col { margin: 0 0 var(--space-4); }
/* the panel's video column is desktop decoration; on a phone it would be a
   silent download for something nobody asked to see */
#menu .knav-col-media { display: none; }

#menu .knav-coltitle {
  font-family: var(--font-display);
  font-weight: var(--subtitle-weight);
  font-stretch: var(--subtitle-stretch);
  font-size: var(--text-base);
  color: var(--mono-6);
  margin: 0;   /* the 44px toggle row carries the rhythm now (below) */
}

#menu .knav-cat {
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-label, 0.08em);
  text-transform: uppercase;
  color: var(--mono-3);
  margin: var(--space-3) 0 var(--space-2);
}

/* The panels' inner accordions (.knav-acc, nav.css) are a DESKTOP
   interaction. Here they would nest a second accordion level inside the
   service-group accordion and bury every link three taps deep, so they are
   neutralised: the toggle renders as the plain heading it replaced (chevron
   gone, clicks dead) and every group stays open. nav.js also sets the
   buttons disabled so the focus trap cannot land on a control that does
   nothing. The ID out-specifies nav.css's class rules regardless of load
   order. */
#menu .knav-acc-toggle { display: flex; width: 100%; background: none; border: 0; text-align: left; pointer-events: none; }
#menu .knav-acc-chev { display: none; }
#menu .knav-acc-panel { display: block; }
#menu .knav-acc-list { overflow: visible; }

#menu .knav-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;          /* tap target */
  padding: var(--space-1) 0;
  color: var(--mono-6);
  font-size: var(--text-base);
  text-decoration: none;
}

#menu .knav-ico { flex: 0 0 auto; width: 1.25rem; height: 1.25rem; }
#menu .knav-ico svg { display: block; width: 100%; height: 100%; }
#menu .knav-txt b { font-weight: var(--weight-medium, 500); }
/* the desktop panel's hover caption has no room and no hover here */
#menu .knav-txt em { display: none; }

/* COLOUR LEAKS from the desktop panel (found 2026-08-31): nav.css paints
   .knav-ico and .knav-txt b with --mono-1 and their hover with --green-3 —
   near-black at rest and 2.9:1 dark green on hover, BOTH wrong on this dark
   curtain (the same green-3-on-dark contrast failure tokens.css 2.6.3
   documents). Rest inherits the link's mono-6; hover takes green-4, the
   dark-ground accent (5.13:1). The desktop hover arrow (.knav-txt b::after)
   is a second link idiom on a surface that already has the underline sweep
   (#menu a::after) — off. */
#menu .knav-ico,
#menu .knav-txt b { color: inherit; }
/* No hover accent and no underline sweep on the accordion links (removed
   2026-08-31, user call): the 44px rows on a dark curtain need no second
   state — quiet is the design. The desktop hover colours are still forced
   off (the inherit above), and the #menu a::after underline idiom stays for
   any future primary-list links, just not here. */
#menu .knav-link:hover .knav-ico,
#menu .knav-link:hover .knav-txt b { color: inherit; }
#menu .knav-txt b::after { display: none; }
#menu .knav-link::after { display: none; }

/* SPACING LEAKS, same source: the desktop coltitle padding
   (--space-2/--space-3) landed on the accordion toggles and stacked with
   the overlay's own margins into the reported "super tall" gaps. The
   toggle becomes a plain 44px row (tap-target floor kept), groups sit a
   step apart, and the group's own rhythm comes from the 44px link rows. */
#menu .knav-acc-toggle { padding: 0; margin: 0; min-height: 44px; align-items: center; }
#menu .knav-acc + .knav-acc { margin-top: var(--space-1); }
#menu .knav-acc-list { padding: 0 0 var(--space-2); }

/* CTA — the .knav-cta button the bar hides below --bp-nav */
.menu-cta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 48px;
  margin: var(--space-5) 0 0;
  padding: 0.6em 1.4em;
  border: 0;
  border-radius: var(--radius-full, 999px);
  background: var(--mono-6);
  color: var(--mono-1);
  font-family: var(--font-display);
  font-weight: var(--subtitle-weight);
  font-stretch: var(--subtitle-stretch);
  font-size: var(--text-base);
  cursor: pointer;
}
.menu-cta .btn-arrow { display: block; width: 1.1rem; height: 1.1rem; }
.menu-cta .btn-arrow svg { display: block; width: 100%; height: 100%; }

/* The groups and the CTA join the same staged arrival as the list, picking up
   where its eighth item leaves off. */
.menu-group,
.menu-cta {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.15s ease, transform 0.15s ease;
}
body:has(#menuToggle input:checked) .menu-group,
body:has(#menuToggle input:checked) .menu-cta {
  opacity: 1;
  transform: translateY(0);
  transition-duration: 0.3s;
}
/* STRUCTURAL, not positional (2026-08-31). These were
   .menu-group:nth-of-type(1)/(2), which counted the menu-list CONTAINER div
   as a sibling — so when the primary list emptied (or a cached page still
   carried it), the groups' positions shifted and one of them lost its delay
   and popped in before the curtain. Adjacent-sibling selectors follow the
   structure wherever it drifts. Delays retimed to the curtain itself
   (lands ~.6s) now that there is no eight-item list to queue behind. */
body:has(#menuToggle input:checked) .menu-group { transition-delay: 0.62s; }
body:has(#menuToggle input:checked) .menu-group + .menu-group { transition-delay: 0.67s; }
body:has(#menuToggle input:checked) .menu-cta { transition-delay: 0.74s; }

#menu li {
  padding: 0;
  margin: 0.35rem 0;
  font-size: var(--text-4xl);
  /* DS subtitle role: Archivo SemiBold, SemiExpanded — the same 600 that
     carries h4–h6, rather than the legacy --heading family with no weight */
  font-family: var(--font-display);
  font-weight: var(--subtitle-weight);
  font-stretch: var(--subtitle-stretch);
  line-height: var(--leading-normal);   /* open, list-like spacing */
  opacity: 0;
  transform: translateY(14px);
  /* closing: gone in 0.15s, before the curtain starts to move */
  transition: opacity 0.15s ease, transform 0.15s ease;
  transition-delay: 0s;
}

body:has(#menuToggle input:checked) #menu li {
  opacity: 1;
  transform: translateY(0);
  transition-duration: 0.3s;   /* arriving is unhurried; leaving is not */
}

/* the links wait for the curtain: the slat sweep runs 0.28s + 7 × 0.045s
   stagger ≈ 0.6s, so the first item starts just after it lands */
body:has(#menuToggle input:checked) #menu li:nth-child(1) { transition-delay: 0.62s; }
body:has(#menuToggle input:checked) #menu li:nth-child(2) { transition-delay: 0.67s; }
body:has(#menuToggle input:checked) #menu li:nth-child(3) { transition-delay: 0.72s; }
body:has(#menuToggle input:checked) #menu li:nth-child(4) { transition-delay: 0.77s; }
body:has(#menuToggle input:checked) #menu li:nth-child(5) { transition-delay: 0.82s; }
body:has(#menuToggle input:checked) #menu li:nth-child(6) { transition-delay: 0.87s; }
body:has(#menuToggle input:checked) #menu li:nth-child(7) { transition-delay: 0.92s; }
body:has(#menuToggle input:checked) #menu li:nth-child(8) { transition-delay: 0.97s; }

/* DS primary in-content link, on a dark ground: inherits the ink, no rest
   underline, hover draws a 2px currentColor rule left → right. The old
   black gradient-sweep was invisible on the dark curtain. */
#menu a {
  position: relative;
  display: inline-block;
  color: var(--mono-6);
  text-decoration: none;
}

#menu a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.08em;
  /* DS "UI element link": a heavier rule than the 2px in-content link, and
     sized in em so it holds its weight against display-scale type; the px
     floor keeps it from thinning out at small sizes */
  height: max(4px, 0.075em);
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--motion-slow, 0.28s) var(--ease-standard, ease);
}

#menu a:hover::after,
#menu a:focus-visible::after { transform: scaleX(1); }

#primary-menu {
	display: flex;
	flex-direction:column;
	align-items: flex-start;   /* each link only as wide as its own text */
}

/* The bar has to survive a dark full-screen overlay under it: the glass goes
   away, the black wordmark inverts to white, the toggle's bars go light (a
   black X on near-black is a menu with no visible way out), and the desktop
   nav items step aside — the overlay replaces them. */
/* The bar while the overlay is up — and for the whole close, which is why
   these key off the body classes scripts.js holds open past the checkbox
   rather than off :has(). Bar stripped to nothing: its glass is a strip
   exactly --knav-h tall, and fading it back in over a retracting curtain was
   the "container the height of the nav appears" on the way out. */
body.menu-open .knav,
body.menu-closing .knav {
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
}

/* toggle bars go light — a black X on near-black is a menu with no visible
   way out; they stay light through the close, while the dark is still there */
body.menu-open #menuToggle span,
body.menu-closing #menuToggle span { background: var(--mono-6); }

/* While the overlay is up the bar carries the toggle and nothing else — the
   logo included (it was inverted to white before; dropped for now). No
   transition-delay trick needed now: the class simply outlives the curtain,
   so the ordinary transition restores them at the right moment. */
.knav-brand,
.knav-items,
.knav-cta {
  transition: opacity var(--motion-medium, 0.25s) ease;
}

body.menu-open .knav-brand,
body.menu-closing .knav-brand,
body.menu-open .knav-items,
body.menu-closing .knav-items,
body.menu-open .knav-cta,
body.menu-closing .knav-cta {
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  /* #menu itself, not just its contents. #menu closes by transitioning
     `visibility` with a 0.78s DELAY, timed to outlast the slat retraction.
     Neutralising only the slats and the links left that delay in place, so
     everything went transparent at t=0 while a 100vw x 100dvh div at z-index
     89 stayed hit-testable for another 745ms — the first click after closing
     the menu was swallowed anywhere below the bar. */
  #menu, .menu-slats i, #menu li, #menu a::after { transition: none; }
}

header input {
	display: none;
}

/* Megamenu: see css/nav.css */

/* Content */ 

/* Was position:absolute, full-width and 400px tall, sitting BEHIND the
   content card (which pulled itself over it with an inline margin-top:-150px
   in single/single-palvelu.php — both removed 2026-08-31). Now a slim strip
   IN FLOW above the content: same min(1535px, gutter-to-gutter) frame as the
   content card so the two edges align, the section radius the card carries,
   and the video cover-cropped inside. The strip clears the fixed masthead
   with its own top margin — the entry-content's old 24rem clearance padding
   shrank with it (below). */
/* The strip TUCKS UNDER the content card (2026-08-31): the card pulls up
   over it by (height - peek), so only a --banner-peek sliver stays visible
   above the card's top edge — a rounded lip, like the next card in a stack.
   Both live on :root so the strip's height and the card's overlap cannot
   drift apart. */
:root {
    --banner-h: clamp(6rem, 14vh, 9rem);
    --banner-peek: 15px;
}

.random-banner-element {
    /* EXACTLY the content card's frame: the card is .entry-content's
       max-width:1535px inside #page's own `padding: 0 var(--page-gutter)`.
       The first cut of this rule subtracted the gutter AGAIN
       (calc(100% - 2*gutter)) on a 100% that was already gutter-inset, which
       left the strip narrower than the card. Same constraint, same edges. */
    width: 100%;
    max-width: 1535px;
    height: var(--banner-h);
    margin: calc(var(--knav-h, 4.75rem) + var(--space-5, 2rem)) auto 0;
    overflow: hidden;
    border-radius: 1em;   /* the section radius, as the card beneath it */
    /* under the card — the card carries z-index 1 (below) */
    position: relative;
    z-index: 0;
}

.random-banner-video {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Content flow spacing (replaces the old gap-divs).
   DS space scale for element gaps; layout rhythm = multiples of --space-section (8rem). */
p + .heading-h3 { margin-top: var(--margin-large); }
figure.wp-block-image + .heading-h2 { margin-top: var(--margin-large); }
p + .btn { margin-top: var(--space-5); display: inline-block; }

/* Template spacing (replaces gap-divs in single/single-palvelu/tag).
   Was calc(var(--space-section) * 3) — 24rem of clearance for the ABSOLUTE
   banner band that used to float behind the content. Zero now: the card
   OVERLAPS the strip (below), so any padding here would push the card down
   and widen the peek past --banner-peek. */
.single-palvelu .entry-content, .single-post .entry-content { padding-top: 0; }

/* The overlap itself: pull the card up over the strip, leaving exactly
   --banner-peek visible. Three classes deep on purpose — it must out-specify
   both `.colorcontainer.content-wrapper-white`'s 12rem margin-top and the
   base .content-wrapper rules. z-index 1 over the strip's 0; the
   .content-wrapper transform gives the card a stacking context anyway, but
   the explicit pair keeps the intent readable. */
.single-palvelu .entry-content > .colorcontainer,
.single-post .entry-content > .colorcontainer {
    margin-top: calc(var(--banner-peek) - var(--banner-h));
    position: relative;
    z-index: 1;
}
.single-palvelu .colorcontainer, .single-post .colorcontainer { padding-bottom: var(--space-5); margin-bottom: var(--margin-x-large); }

/* Landing-page section rhythm (replaces gap-divs) */
/* 3.5x was never rhythm — it was clearance. The hero video used to be
   position:absolute with margin-top:26em, so it took no space and the copy
   had to be pushed 28rem down to miss it. The band is in flow now and
   occupies its own height, so this is an ordinary section gap. Safe to
   change globally: .hero-text-container appears on exactly the five pages
   that carry a hero container, always immediately after it. */
.hero-text-container { margin-top: var(--space-section); margin-bottom: calc(var(--space-section) * 2.5); }
.colorcontainer.content-wrapper-white { margin-top: calc(var(--space-section) * 1.5); }
.colorcontainer.staff-card { margin-top: var(--margin-x-large); }
.heading-h2 + .wp-block-columns { margin-top: var(--margin-large); }
.heading-h2 + p { margin-top: var(--margin-large); }
.wp-block-columns + .btn { margin-top: var(--margin-large); display: inline-block; }
.content-wrapper .acc-container { margin-top: var(--margin-x-large); }
.page .entry-content { padding-bottom: calc(var(--space-section) * 1.5); }
.page-top { margin-top: calc(var(--space-section) * 2); }

/* Nav clearance for the three templates that never had any. .knav is
   position: fixed and .knav-sizer reserves nothing (left: -9999px), so every
   page's clearance comes from INSIDE its content: .page-top's 16rem above,
   .single-palvelu/.single-post .entry-content's 24rem, or
   #tag-list-container's own padding. 404.php, index.php and
   archive-palvelu.php match none of those, so their content began at y = 0
   behind the 76px glass bar — /404/, whose whole document is one h1, read as
   a blank page. These three wrappers appear nowhere else in the theme and
   carry no other rules, so this cannot reach a template that already clears
   the bar. */
.site-main,
#main-content {
  padding: calc(var(--knav-h, 4.75rem) + var(--space-section)) clamp(1.5rem, 5vw, 5rem) var(--space-section);
}
.colorcontainer section:has(> .btn-centered:last-child) { padding-bottom: var(--margin-x-large); }
.btn-centered + .notification-container { margin-top: calc(var(--space-section) * 1.5); }

/* Lead paragraph — intro copy (ex-h3 markup): base face at lg, keeps the h3 rhythm slot */
p.lead {
	font-size: var(--text-lg);
	margin: 0 0 var(--margin-large);
}

/* Hero-level lead (page subtitles) — one step up */
p.lead-xl {
	font-size: var(--text-xl);
}

/* Heading rhythm (replaces the old gap-divs: h4→2rem, h2→2rem, h3→4rem) */
.heading-h2 {
	margin:0 0 var(--space-5);
}

.heading-h3 {
	margin:0 0 var(--margin-large);
}

.heading-h4 {
	text-transform:uppercase;
	letter-spacing: var(--tracking-label);
	margin:0 0 var(--space-5);
}

.entry-content {
	display: flex;
  flex-direction: column;
  margin: 0;
  align-items: center;
	max-width:1535px;
}

.content-wrapper-white {
	background:#fff;
	border-radius:1em;
	padding:4em 5em;
}

.info-container {
	background: var(--color-ash);
  color: #050505;
  padding: 1rem 3rem;
  border-radius: 1rem;
}

.notification-container {
	border:1px solid var(--color-black);
	padding: 1em;
	text-align:center;
	border-radius:var(--radius-small);
	background:white;
}


/* "Palvelu"-ctp specific */

.meta {
	display:flex;
	margin-bottom:var(--margin-large);
	text-align:right;
}

.tags {
	margin-left:var(--margin-xx-large);
	width:100%;
}

.ad-libris {
    height: 30px;
}

body.single-palvelu ul {
  list-style: none;
}

body.single-palvelu .entry-content ul li::before {
  content: "";
  display: inline-block;
  width: 16px;   
  height: 16px;  
  margin-right: 0.5em;
  background-image: url("https://kapta.fi/wp-content/uploads/2025/02/arrow-right.png");
  background-size: contain;
  background-repeat: no-repeat;
  vertical-align: middle;
	 transform: rotate(-35deg); 
}

body.single-palvelu .entry-content ul.no-lst li::before {
  content: "";
  width: 0px;   
  height: 0px;  
  margin-right: 0em;
  background-image: none; 
}

/* Hero — full-bleed scanline: container breaks out of the page column to
   span the exact viewport width; the mask stretches edge-to-edge. */

/* Anchored to the document origin (no positioned ancestor exists): exact
   viewport width, flush to the top edge, under the fixed nav.

   THE BOX SPANS THE VIEWPORT; THE INK DOES NOT, AND THAT IS THE CHOICE.
   The artwork does not fill its own viewBox — the ink spans x 35.6–2742.2 of
   2791 (96.976%), so ~1.28% is empty on the left and ~1.75% on the right,
   about 18px and 25px at a 1440px viewport.
   Scaling the box to 103.12vw and offsetting by the left ink margin brings
   the ink flush to both edges. That has now been built TWICE and reverted
   TWICE (2026-08-30 the second time, on the judgement that the inset simply
   reads better). Do not implement it a third time as though it were an
   oversight — it is a decision. The measurement is recorded here so the
   reasoning survives without the code. */
#hero-logo-container {
  position: absolute;
  /* lifted 7.5% of its own height above the page top */
  top: calc(100vw * 1536 / 2791 * -0.075);
  /* --hero-shift stays the single visual correction knob. */
  --hero-shift: 0px;
  left: var(--hero-shift);
  width: 100vw;
  aspect-ratio: 2791 / 1536;   /* = the SVG crop (viewBox 0 0 2791 1536) */
  margin: 0;
  /* own stacking context: contains the pillars' z-index 3 so the slats can
     never paint over the riser card (z-index 2) */
  z-index: 1;
    }

/* Card position derives from the ARTWORK, not the viewport (viewport-height
   anchors are unstable across resize and mobile chrome — best practice is
   content-geometry anchoring). Requirement: the logo (= artwork center) sits
   vertically centered between the nav bottom (4.75rem) and the card top.
   Hero top = −0.075·H, so logo center = 0.425·H, and
   cardTop = 2·(0.425·H) − 4.75rem = 0.85·H − 4.75rem, where
   H = 100vw·1536/2791. Pure width math — resize-stable by construction.
   (Keep 0.85 = 2 × (0.5 + heroTopFactor) in sync with the hero's top.)
   NOTE this duplicates the hero's own height expression. It is only correct
   while the hero box is exactly 100vw — if that ever changes, this must move
   with it or the card drifts out of alignment with no error anywhere. */
.home .entry-content {
  padding-top: calc(100vw * 1536 / 2791 * 0.85 - 4.75rem);
}

/* First section (.hero-riser, tagged by scripts.js): hidden until the
   entrance completes (.nav-in), then rises over the hero in step with the
   nav fade.

   ONE card, copy across its full width (2026-08-28). It was briefly split
   into a copy card + an empty quarter-card; that plan was dropped. The old
   75% cap on .riser-copy went with it — the right quarter is no longer
   reserved for anything, so the copy simply spans the card. */
body.home .hero-riser {
  position: relative;
  z-index: 2;
  /* divider card between hero and content — same skin as the cards below
     (.content-wrapper-white: #fff, radius 1em, padding 4em 5em) */
  width: 100%;
  background: #fff;
  border-radius: 1em;
  padding: 4em 5em;
  opacity: 0;
  transform: translateY(90px);
  transition: opacity .7s ease, transform .7s var(--ease-standard);
}

/* the flex row stays for one reason only: it is what makes the
   `section > p { display: contents }` guard below work on wpautop's stray
   paragraphs. With a single full-width child it does nothing else. */
body.home .hero-riser section {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  height: 100%;
}

body.home .hero-riser .riser-copy {
  flex: 1 1 auto;
  min-width: 0;
}

body.home .hero-riser .riser-copy p {
  margin-bottom: 0;
}

/* The lead. Scoped to the riser: p.lead is shared with the service pages,
   which have a narrower measure and must not move. This rule (0,4,2) also
   supersedes the global p.lead / p.lead-xl sizes — the markup carries
   `lead lead-xl` to match the service pages, but on the front page that class
   is inert and the clamp below is what sizes this text.

   ONE STEP UNDER THE HEADING, which is just the DS type scale doing its job.
   The ceiling used to be --text-2xl, and that IS the h2's own size: at the top
   of the range the two rendered at the same size, and once the lead went
   SemiExpanded there was nothing but weight left between them. --text-xl is
   the next step down (36px → 28px), so hierarchy is carried by size AND
   weight again — two signals, as before the stretch change, with size now
   doing the work stretch used to.

   MEASURE HAS TO BE CAPPED DIRECTLY NOW, and this is what the size drop would
   otherwise break. .entry-content caps at 1535px and the card takes 5em of
   padding a side, so the full-width measure runs to ~1375px: that is ~76
   characters a line at 36px, inside the DS readable band of 65–75, but ~98 at
   28px, well outside it. The old clamp kept the measure honest BY being
   large. Shrink the type and the line has to be bounded on its own.
   68ch, not --container-text: that token is 38rem, calibrated for 16px body
   copy, and at 28px it would cut the line to ~43 characters. ch is relative
   to the font's own "0" advance, so the cap tracks the type size through the
   clamp instead of fighting it.
   The --text-lg floor still applies on phones, where 28px in a narrow card
   would be a handful of words a line. */
body.home .hero-riser .riser-copy p.lead {
  font-size: clamp(var(--text-lg), 2.6vw, var(--text-xl));
  line-height: var(--leading-snug);
  max-width: 68ch;
  /* SemiExpanded — 112.5% on Archivo's wdth axis, which --font-body shares
     with --font-display, so the width axis works on body copy directly.
     --subtitle-stretch rather than a new token: title, subtitle and eyebrow
     are all 112.5% already, and "subtitle" is the closest role to a lead
     sitting under the card's h2. */
  font-stretch: var(--subtitle-stretch);
}

/* stray wpautop <p> direct children (empty formatting artifacts) must not
   become flex items — dissolve them (.riser-copy's own <p> is untouched) */
body.home .hero-riser section > p {
  display: contents;
}

body.home.nav-in .hero-riser {
  opacity: 1;
  transform: none;
}

/* Scroll-out pillars: paper-colored slats that rise over the hero in
   sequence (built by scripts.js). Wrapper clips them while parked below. */
.hero-pillars {
  position: absolute;
  inset: 0;
  display: flex;
  overflow: hidden;
  pointer-events: none;
  z-index: 3;
}

.hero-pillars i {
  flex: 1 1 0;
  background: var(--color-bg);
}

/* Client ribbon — full-bleed rolling marquee: duplicated track slides half
   its own width per cycle for a seamless loop. Names in the muted subtitle
   voice (the "logo wall in gray" convention). Rolls continuously. */
.client-ribbon {
  position: relative;
  /* full-bleed breakout: needs block-style positioning — .entry-content is a
     centering flex column, and align-items:center would center the margin box
     (negative margin included), shoving the ribbon left and leaving a gap on
     the right. flex-start restores edge-relative margin math. */
  align-self: flex-start;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-top: var(--margin-large);
  padding: var(--space-4) 0;
  overflow: hidden;
}

.ribbon-track {
  display: flex;
  width: max-content;
  animation: ribbon-roll 60s linear infinite;
}

.ribbon-track span {
  font-family: var(--font-display);
  font-weight: var(--subtitle-weight);
  font-stretch: var(--subtitle-stretch);
  font-size: var(--text-lg);
  color: var(--mono-3);
  white-space: nowrap;
}

.ribbon-track span::after {
  content: "\00B7";   /* middot separator */
  margin: 0 var(--space-5);
  color: var(--mono-4);
}

@keyframes ribbon-roll {
  to { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .ribbon-track { animation: none; }
  .link-pill, .link-pill .pill-arrow { transition: none; }
}

/* Statement — quote below the ribbon: words fill mono-4 → mono-1 scrubbed
   to scroll (scripts.js, .statement). The faint state is applied BY the JS,
   so no-JS and reduced-motion readers get plain full-ink text.
   The block IS the standard content container (100% of .entry-content,
   capped at its 1535px max); the display size is viewport-scaled so ~⅓ of
   the quote fills each line across it. */
/* Card stack (scripts.js .card-stack): pinned deck — the active card sits
   under the nav, the waiting cards peek at the viewport bottom; a spent
   scroll segment snaps the next card in (hero snap recipe). Without JS, on
   mobile and under reduced motion the cards simply flow. */
.card-stack {
  width: 100%;
  margin: calc(var(--space-section) * 1.5) 0;
  /* the two numbers the framing is built from — declared here so the frame
     and the cards inside it cannot drift apart */
  --stack-inset: 1vh;      /* gap under the nav, matched at the viewport foot */
  --stack-radius: 1em;     /* one radius for the cards and for the frame */
}

.stack-viewport {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.stack-card {
  /* semantic tokens: the cards carry .theme-dark (DS 2.6 inverted-surface
     scope), so these resolve to the dark remap */
  background: var(--color-surface);
  color: var(--color-ink);
  border-radius: var(--stack-radius, 1em);
  padding: 4em 5em;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  min-height: 18rem;
  /* deck distinction, whisper-quiet: standard border (one shade off the
     surface in the .theme-dark scope) plus the DS top-edge shadow */
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-edge);
}

/* Quote card: a massive opening-quote glyph forms a left column; the quote
   and cite stack in the right one. Boldness spent on scale alone — the tone
   stays quiet (border-strong gray, mono-2 in the dark scope). The glyph is
   a real element (.quote-mark, aria-hidden) rather than a pseudo so the
   ink-fill scrub can flow through it (scripts.js). */
.stack-card-quote {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  align-content: start;
  column-gap: clamp(1rem, 3vw, 3rem);
}

.stack-card-quote .quote-mark {
  grid-row: 1 / span 2;
  font-family: var(--font-display);
  font-weight: var(--title-weight, 700);
  font-size: clamp(9rem, 16vw, 17rem);
  line-height: 0.7;  /* the glyph sits at cap height — tighten so it hugs the card top */
  color: var(--color-border-strong);
}

/* Palvelut pill field: every nav link, harvested live by scripts.js, as
   round b/w pills filling the card below the centered title. DS arrow-reveal
   button, white variant (recorded in brand.md / llms.txt — the knav CTA is
   the black-on-light case, this is the dark-ground case): ink/surface swap
   so the pair auto-inverts with the card's theme scope; hover dims one mono
   step (6 → 5, mirroring the CTA's 1 → 2) while the Lucide arrow extends.
   Excluded from the global green link scheme per the DS component-link
   rule. */
.statement.statement-center {   /* double class: .statement's later text-align:left must lose */
  text-align: center;
}

.pill-field {
  flex: 1;   /* the field owns the rest of the card */
  margin-top: var(--space-4);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-evenly;   /* columns still spread across the width */
  /* Rows PACK from the top at their natural gap. Both distributing values
     were tried and both fail on a card this tall: space-evenly reserved a
     gap above the first row and held the links away from the title;
     space-between then pushed that same free height BETWEEN the rows and
     pulled the block apart. There is far more card than there are pills, so
     any value that distributes the slack has to put it somewhere visible —
     flex-start puts it at the foot, where it reads as margin rather than as
     the rows drifting apart. */
  align-content: flex-start;
  /* Row gap is now set deliberately rather than left at the old 0.5em (~9px),
     which packed the rows too tight once they stopped being spread apart.
     --space-4 (24px) is a little over half a pill's height — the DS added
     that step precisely for component work like this, where 16 is tight and
     32 is loose. The column gap stays a minimum; space-evenly sets the real
     horizontal spacing. */
  gap: var(--space-4) 0.6em;
}

.link-pill {
  display: inline-flex;
  align-items: center;
  /* right pad reads together with the reserved arrow box — kept slimmer so
     the resting pill doesn't look lopsided */
  padding: 0.55em 0.7em 0.55em 1.3em;
  border-radius: var(--radius-full, 999px);
  /* gray pill on the dark card (DS: primary, dark ground). Rest sits a
     shade under mono-2; hover LIFTS a shade past it (mixes — no tokens at
     these steps).
     Text runs the theme's ink scale rather than a hard-coded white: white
     at rest has nowhere left to go, so the label could not light up with
     the fill. Rest sits between the muted and secondary inks — ink-2 alone
     (mono-4 #d9d9d9 here) is 85% of the way to white and read as white,
     while ink-3 alone (mono-3) drops to 4.2:1 on this fill, under AA for
     22px text. The mix lands ≈#adadad, ~6.5:1 on the fill: unmistakably
     gray, with real distance left to travel. Semantic at both ends, so the
     pair inverts with the scope instead of being pinned to a dark card. */
  background: color-mix(in srgb, var(--mono-2) 70%, var(--mono-1));
  color: color-mix(in srgb, var(--color-ink-3) 55%, var(--color-ink-2));
  font-family: var(--font-display);
  font-weight: var(--weight-medium);
  font-size: var(--text-lg);
  line-height: 1.2;
  white-space: nowrap;
  text-decoration: none;
  transition: background .2s ease, color .2s ease;
}

/* the arrow's box is RESERVED (unlike the widening CTA): in a dense pill
   field a growing pill shoves its whole row sideways — so the footprint
   never changes and the icon rises in on hover, the in-content-link icon
   recipe */
.link-pill .pill-arrow {
  display: inline-flex;
  align-items: center;
  width: 1em;
  margin-left: 0.35em;
  opacity: 0;
  transform: translateY(0.25em);
  transition: opacity .2s ease, transform .2s ease;
}

.link-pill .pill-arrow svg {
  width: 1em;
  height: 1em;
  display: block;
}

/* fill and label light up together — the label carries the arrow with it
   (currentColor), so the whole pill brightens as one */
.link-pill:hover,
.link-pill:focus-visible {
  background: color-mix(in srgb, var(--mono-2) 70%, var(--mono-3));
  color: var(--color-ink);
}

.link-pill:hover .pill-arrow,
.link-pill:focus-visible .pill-arrow {
  opacity: 1;
  transform: translateY(0);
}

/* End-of-stack exit: paper slats sweep over the pinned deck (scripts.js),
   the hero pillar recipe verbatim. Wrapper clips them while parked below. */
.stack-pillars {
  position: absolute;
  inset: 0;   /* the frame's overflow now clips the --shadow-edge bleed that
                 used to poke past the slats, so no side overhang is needed */
  display: flex;
  overflow: hidden;
  pointer-events: none;
  z-index: 10;   /* above the deck (cards stack at 1..N) */
}

.stack-pillars i {
  flex: 1 1 0;
  background: var(--color-bg);
}

/* slats overlap 1px so flex rounding can't open seams over the dark cards */
.stack-pillars i + i {
  margin-left: -1px;
}

/* Whatever follows the pinned stack begins almost immediately. The exit
   sweep already ENDS on blank paper, so a full section rhythm on top of that
   is dead scroll over a gap the composition has given you twice — the stack
   "disappears" and the next thing should be right there.
   Deliberately `+ *`: this belongs to the stack's exit, not to any one
   section. The first version targeted `.nps-figure` by name and silently
   stopped matching the moment the carousel was inserted between them, which
   put the 192px gap straight back. `.card-stack.stack-on` (0,2,0) also
   outranks any single-class section margin regardless of source order. */
.card-stack.stack-on + * {
  margin-top: var(--space-5);
}

/* Enhanced mode (desktop + GSAP): the stack pins and cards go absolute.
   .stack-viewport becomes the FRAME — the band the whole assembly lives in,
   held off the viewport foot by exactly the gap it keeps under the nav, and
   carrying the card radius so every edge clipped against it still reads as
   a card corner. The frame is what bounds the stack now: the cards simply
   fill it and let it do the cropping. */
.card-stack.stack-on {
  margin: 0;
  padding: calc(var(--knav-h, 4.75rem) + var(--stack-inset)) 0 var(--stack-inset);
}

.stack-on .stack-viewport {
  display: block;
  position: relative;
  height: calc(100vh - var(--knav-h, 4.75rem) - var(--stack-inset) * 2);
  border-radius: var(--stack-radius);
  /* the deck's bodies (and an activated card's overhang) stop here */
  overflow: hidden;
}

.stack-on .stack-card {
  cursor: pointer;   /* clicks advance the stack (scripts.js) */
  position: absolute;
  /* the frame already holds the insets — an active card simply fills it */
  top: 0;
  left: 0;
  right: 0;
  height: 100%;
  margin: 0;
  overflow: hidden;
  will-change: transform;
}

/* No opacity anywhere on the cards (user, 3.17.61): every card is fully
   opaque at every position. The resting-card wash (.85, 3.17.56) is gone —
   depth is carried by the tonal stepping alone (gsap backgroundColor,
   stackBg()), never by transparency. */

/* hover light-up for every resting card — trail and deck alike, one card at
   a time. An additive white veil (::after) replaced the brightness filter:
   resting cards sit at different grays and
   a multiplicative filter lifted each by a different amount, so the
   lightnesses didn't follow suit — the veil adds the same light to all.
   It also sidesteps gsap's inline backgroundColor. */
.stack-on .stack-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  transition: opacity var(--motion-fast, 0.15s) ease;
  pointer-events: none;
}

.stack-on .stack-card.stack-trail:hover::after,
.stack-on .stack-card.stack-deck:hover::after {
  opacity: 0.1;
}

.statement {
  font-family: var(--font-display);
  font-weight: var(--subtitle-weight);
  font-stretch: var(--subtitle-stretch);
  font-size: var(--text-display);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  margin: 0;
  text-align: left;
  text-wrap: balance;
}

/* sub-display statement: same voice at ~⅔ scale, for longer texts that
   must fit the fixed card height (card 2) */
.statement-sub {
  font-size: clamp(1.75rem, 4.4vw, 4.5rem);
}

/* wpautop wraps the quote text in a <p>; the generic p rule would reset it */
.statement p {
  font: inherit;
  font-stretch: inherit;
  letter-spacing: inherit;
  text-wrap: inherit;
  margin: 0;
}

/* DS primary in-content link (recorded in brand.md / llms.txt): ink-colored
   — inherits the surrounding text (here: rides the ink-fill), no rest
   underline; hover draws a line expanding left → right. Excluded from the
   global green link scheme per the DS component-link rule. */
.statement-cite a {
  color: inherit;
  text-decoration: none;
  position: relative;
  transition: color var(--motion-fast, 0.15s) ease;
}

/* hover jumps to full ink immediately — the scrub owns the resting color
   (inline, on the parent cite), but a rule on the <a> outranks inheritance */
.statement-cite a:hover {
  color: var(--color-ink);
}

.statement-cite a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.12em;
  height: 2px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--motion-slow, 0.28s) var(--ease-standard, ease);
}

.statement-cite a:hover::after {
  transform: scaleX(1);
}

/* Lucide external-link, right of the name — space reserved, fades in
   upward on hover */
.statement-cite .cite-ext {
  display: inline-block;
  width: 0.7em;
  height: 0.7em;
  margin-left: 0.3em;
  opacity: 0;
  transform: translateY(0.25em);
  transition: opacity var(--motion-fast, 0.15s) ease,
              transform var(--motion-fast, 0.15s) ease;
}

.statement-cite .cite-ext svg {
  display: block;
  width: 100%;
  height: 100%;
}

.statement-cite a:hover .cite-ext {
  opacity: 1;
  transform: translateY(0);
}

.statement-cite {
  margin: var(--space-4) 0 0;
  font-size: var(--text-2xl);
  color: var(--color-ink-3);   /* semantic — adapts inside .theme-dark */
  text-align: left;
}

/* Feedback carousel — customer quotes in a row that BEGINS at the container's
   left edge (flush under the card stack, nothing to its left) and runs off
   the right edge of the screen, so the next card is always visibly queued.
   Built on native overflow scrolling + scroll-snap rather than a slider:
   touch, trackpad, shift-wheel and keyboard all work without reimplementing
   them, and the arrow is a convenience on top rather than the only way in. */
.quote-carousel {
  /* full-bleed breakout (the .client-ribbon pattern — see the note there on
     why flex-start is required), then padded back in on the left only, so
     card 1 lines up with the stack above while the row runs off to the right */
  align-self: flex-start;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  padding-left: calc(50vw - 50%);
  margin-top: calc(var(--space-section) * 1.5);
}

.qc-track {
  display: flex;
  gap: var(--space-5);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* the arrow is the visible control; a scrollbar under a full-bleed row
     reads as page chrome */
  scrollbar-width: none;
  cursor: grab;   /* the row is draggable — scripts.js */
  /* Slight feather on the right: the row runs to the screen edge, so without
     it the queued card is guillotined by the viewport. A MASK rather than an
     overlay gradient — it fades to whatever is actually behind the track,
     instead of to a hard-coded paper colour that would go wrong the moment
     the page ground changes. The mask sits on the padding box, so it stays
     pinned at the edge while the content scrolls under it. */
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 5rem), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 5rem), transparent);
}

/* While a hand is on it, both scroll aids have to stand down: snap yanks the
   track back on every frame the pointer moves it, and smooth would animate
   each scrollLeft assignment against the next one. Selection off too, or the
   drag paints the quotes blue. Restored on release, where scripts.js settles
   the row onto the nearest card itself. */
.qc-track.is-dragging {
  cursor: grabbing;
  scroll-snap-type: none;
  scroll-behavior: auto;
  user-select: none;
  -webkit-user-select: none;
}

.qc-track::-webkit-scrollbar { display: none; }

/* a scroll container must be reachable by keyboard — Chrome only does this
   for itself while the slides hold nothing focusable, which stops being true
   the moment a card gains a link */
.qc-track:focus-visible,
.qc-next:focus-visible {
  outline: var(--focus-width, 2px) solid var(--color-focus);
  outline-offset: var(--focus-offset, 2px);
}

.qc-card {
  flex: 0 0 auto;
  width: min(42rem, 82vw);
  scroll-snap-align: start;
  /* #fff, not --color-surface: the light surface token IS the page ground
     (mono-6), so a card painted with it would vanish. Same call the riser
     and content cards make. */
  background: #fff;
  border-radius: var(--stack-radius, 1em);
  padding: 3em 3.25em;
  display: flex;
  flex-direction: column;
  /* quote at the top, attribution pinned to the foot — cards stretch to a
     common height, so ragged quote lengths still line their names up */
  justify-content: space-between;
  gap: var(--space-5);
}

.qc-quote,
.qc-quote p {
  margin: 0;
  font-size: var(--text-xl);
  line-height: var(--leading-snug);
  color: var(--color-ink);
  text-wrap: pretty;
}

.qc-by {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.qc-name {
  font-family: var(--font-display);
  font-weight: var(--subtitle-weight);
  font-stretch: var(--subtitle-stretch);
  font-size: var(--text-base);
  color: var(--color-ink);
}

.qc-title {
  font-size: var(--text-sm);
  color: var(--color-ink-3);
}

/* the control sits on the container's LEFT edge, flush under card 1 and the
   card stack above it — the section's own padding-left already lands there,
   so the row needs no inset of its own. (The glyph still points forward: the
   button's job is unchanged by where it sits.) */
.qc-controls {
  display: flex;
  justify-content: flex-start;
  margin-top: var(--space-5);
}

/* same circle as the back-to-top control (.scroll-up) — one arrow glyph,
   rotated. NOTE: .scroll-up is legacy, not a DS component; if a third round
   icon button appears, this pair should be promoted to the DS. */
.qc-next {
  width: 45px;
  height: 45px;
  padding: 6px;
  border: 0;
  border-radius: 50%;
  background: #000;
  cursor: pointer;
  transition: background 0.25s ease-in-out 25ms;
}

.qc-next svg {
  display: block;
  width: 100%;
  height: 100%;
  transform: rotate(90deg);   /* the back-to-top glyph, turned to point forward */
}

.qc-next path { fill: #fff; transition: 0.25s ease-in-out 25ms; }
.qc-next:hover { background: #eee; }
.qc-next:hover path { fill: #000; }

@media (prefers-reduced-motion: reduce) {
  .qc-track { scroll-behavior: auto; }
  .qc-next, .qc-next path { transition: none; }
}

/* NPS figure — last section before the footer. Deliberately NOT coupled to
   the card stack: it scrolls with the page like any other section. No card —
   the figure sits straight on the page ground. Two columns, both vertically
   centred; the count runs once as it comes into view (scripts.js). */
.nps-figure {
  width: 100%;
  display: grid;
  grid-template-columns: auto 1fr;   /* the figure takes what it needs */
  align-items: center;
  column-gap: clamp(2rem, 6vw, 7rem);
  margin: calc(var(--space-section) * 1.5) 0;
}

/* number and letters share a baseline — one figure, read left to right */
.nps-stat {
  display: flex;
  align-items: baseline;
  gap: clamp(0.75rem, 2vw, 2rem);
}

.nps-num,
.nps-label {
  font-family: var(--font-display);
  font-weight: var(--title-weight);
  font-stretch: var(--title-stretch);
  font-size: clamp(4rem, 12vw, 12rem);
  line-height: 0.82;
  letter-spacing: var(--tracking-tight);
}

.nps-num { color: var(--color-ink); }

/* same scale, quieter tone — the number leads and the letters name it
   (the giant quote mark's treatment: bold in size, restrained in colour) */
.nps-label { color: var(--color-border-strong); }

/* The row must not move while the count runs. Two separate problems:
   tabular-nums equalises the DIGITS, but the digit COUNT still grows (0 → 9
   → 99 → 100), which would shove "NPS" sideways twice at display size. So
   the box is reserved at its final width and the number fills it from the
   right — the label never moves, and the count arrives where it will end. */
.nps-count {
  font-variant-numeric: tabular-nums;
  display: inline-block;
  min-width: 3ch;
  text-align: right;
}

.nps-note p {
  font-size: var(--text-xl);
  line-height: var(--leading-normal);
  color: var(--color-ink-2);
  max-width: 32ch;
  margin: 0;
}

@media screen and (min-width: 0px) and (max-width: 991.98px) {
  .nps-figure {
    grid-template-columns: 1fr;
    row-gap: var(--space-5);
  }
}

/* Recorded-loop mode (reserve): the baked composition as a looping video
   (aspect matches the container exactly, so fill = 1:1) */
#hero-loop {
  object-fit: fill;
}

/* The ink video: GL mode uses it as an offscreen texture source (the opaque
   canvas covers it); live mode shows it behind the SVG mask. */
#hero-logo-container video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
}

#hero-logo-container.hero-ready video {
  opacity: 1;
}

/* GL mode: the video is only the shader's texture source — never shown as an
   element (prevents it flashing through the canvas's entrance fade and
   peeking out as a 1px sliver at device-pixel rounding edges) */
#hero-logo-container.hero-gl video {
  opacity: 0;
}

/* Entrance: the artwork fades up, and the nav and first card join the same
   gesture (scripts.js adds .nav-in partway through). */
#hero-scanline,
#hero-logo-container .scanline-gl {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.5s ease;
}

#hero-logo-container.hero-ready #hero-scanline,
#hero-logo-container.hero-ready .scanline-gl {
  opacity: 1;
}

/* Slat entrance (scripts.js): the pillars cover the artwork until it is
   ready, then drop away — the canvas must be at full opacity behind them
   the moment they open, so the fade is skipped entirely. */
#hero-logo-container.hero-slat-entrance #hero-scanline,
#hero-logo-container.hero-slat-entrance .scanline-gl {
  transition: none;
}

/* Nav joins the hero's fade (front page only): hidden until scripts.js adds
   .nav-in to body partway through it, then fades down into place */
body.home .knav {
  opacity: 0;
  transform: translateY(-14px);
  transition: opacity .7s ease, transform .7s var(--ease-standard);
}

body.home.nav-in .knav {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  #hero-scanline, #hero-logo-container .scanline-gl { transition: none; }
  body.home .knav { opacity: 1; transform: none; }
  body.home .hero-riser { opacity: 1; transform: none; }
}

/* Hero cutoff — PHONES ONLY (was 992px, which switched the artwork off for
   every tablet). Tablets now get the full hero, so these two rules moved out
   of the 991.98px block into their own query. The 768px figure is mirrored
   in scripts.js (HERO_DESKTOP) and in header.php's preload `media` attrs —
   all three must agree, or the engine mounts with nothing to show or the
   content padding leaves a hole where the artwork would have been. */
@media screen and (max-width: 767.98px) {
  #hero-logo-container { display: none; }
  /* no artwork above the fold, so the content starts on plain section rhythm
     instead of clearing the hero's width-derived height */
  .home .entry-content { padding-top: var(--space-section); }
}

.entry-content section:first-of-type {
	  transform: translateY(20px);
  	animation: fadeInUp 1.5s ease-out forwards;
}

.staff-card {
	padding:0;
}

.staff-card-column-left {
	flex-grow:0.5 !important;
}

.staff-card-column-right {
	flex-grow:1.5 !important;
	padding:1em;
	padding-right:2em;
}

.staff-card-full-width {
	width:100%;
	padding:0.75em 0.75em 0.75em 1.75em;
}

.staff-card li {
	font-size:var(--size-p-small);
	margin-top:0.75em;
}

.staff-card li:nth-child(1) {
	font-size:var(--size-h4);
	font-weight:600;
}

.portrait {
	margin:0 !important;
}

.portrait img {
	border-radius: 1rem 0rem 0rem 1rem;
}

/* Palvelumme-page, Esittely-page */

/* Page hero video (palvelumme, esittely, varainhankinta, markkinointi,
   myynti) — a video BAND forming the top edge of the main content area.

   IN FLOW, and that is the whole point. It was a position:absolute box that
   had to RECONSTRUCT the content column by arithmetic, and it got it wrong
   twice. First as a hard `width: 1535px`: nothing in the theme is positioned
   (#page carries a z-index but no `position`, which creates no containing
   block), so the containing block was the initial one and the box laid out
   from its static position — 1535px from there overruns the viewport below
   ~1583px, and `body { overflow-x: hidden }` CLIPS rather than scrolls, so
   the video was cut off with no scrollbar to reveal it (992px wide: 573px of
   800px gone). Then, "fixed" at 3.20.1, as
   `width: min(1535px, calc(100% - var(--page-gutter) * 2))` — which anchors
   to the WRONG GUTTER. .site-main is padded `clamp(1.5rem, 5vw, 5rem)`;
   --page-gutter is `clamp(1.25rem, 3vw, 3.5rem)`. At 1440px the column runs
   72..1368 and that box ran 43.2..1396.8, so it still hung 28.8px past the
   content edge on each side — and `justify-content: right` put the video
   against exactly the edge that overhung.

   A block in normal flow inside .site-main fills the content box BY
   DEFINITION. There is no width to compute, no gutter to name, and no way
   for a future change to .site-main's padding to break it. If a layout rule
   has to restate a parent's geometry, that restatement is the bug waiting to
   happen — this one waited through two attempts.

   The mask is gone with the shape: the logo-mark mask (and the circle on
   esittely) framed an 800x600 object, and nothing carries over to a band. */
#hero-palvelumme-container,
#hero-esittely-container {
  display: block;
  overflow: hidden;        /* crops the video into the radius */
  border-radius: 1em;      /* the section radius, as .footer-card and the
                              stack's cards use */
  opacity: 0;
  transform: translateY(-20px);
  animation: fadeInDown 2.5s ease-out forwards;
}

/* A second copy of the logo mark, sibling to the video inside the container.
   It was a flex item next to the video in the old right-justified row — a
   50vw x 50vh box, so on its own it made the row's intrinsic width
   800px + 50vw and was a large part of what overflowed. It draws the same
   mark the video used to be masked with, i.e. it is the pre-mask approach
   left in place. Hidden rather than deleted from the five content files:
   this is a theme-layout decision and it belongs in the theme. */
#hero-palvelumme-container > svg,
#hero-esittely-container > svg { display: none; }

    @keyframes fadeInDown {
      0% {
        opacity: 0;
        transform: translateY(-20px); 
      }
			50% {
    		opacity: 0; 
    		transform: translateY(-20px);
 		 }
      100% {
        opacity: 1;
        transform: translateY(0); 
      }
    }

/* One rule for both pages now. They differed only in the mask they wore —
   the logo mark on palvelumme, a circle on esittely — and a band wears
   neither. `display: block` matters: a video is replaced-inline by default
   and would sit on a text baseline, leaving a few px of descender gap
   between the band's bottom edge and the container's. */
#hero-palvelumme-container video,
#hero-esittely-container video {
  display: block;
  width: 100%;
  /* A band is defined by its height, not its aspect: the column width varies
     by ~400px across the desktop range and a fixed ratio would swing the
     band from a strip to a slab. 26vw tracks the column, floored and capped
     so it stays a band at both ends. */
  height: clamp(12rem, 26vw, 22rem);
  object-fit: cover;
}

#hero-palvelumme, #hero-esittely {
  display: none; /* Hide the SVG */
}

.hero-text-container {
	padding-right:50%;
}

/* Blog */

.content-blog .content-right h1 {
	font-family: var(--heading-expanded);
}

.content-blog .content-right h2 {
	  font-family: var(--heading-expanded);
    margin-top: 1.5em;
    margin-bottom: 1em;
    font-size: 2.5em;
}

.content-blog .content-right p {
	font-size: var(--size-p);
	line-height:1.75em;
}

.content-blog .content-left ul {
	margin-top:0.75em;
}

.content-blog .content-left li {
	font-family: var(--heading-expanded);
	font-size: var(--size-p-small);
	margin-bottom:0.25rem;
	text-align:right;
}
	
/* Tag archive page */

#tag-list-container {
	padding: calc(var(--space-section) * 2) 0;
}

#tag-list-container h2 {
	font-size:var(--size-h2);
	margin-bottom:var(--margin-large);
}


#tag-list-container ul {
	width:100%;
}

#tag-list-container li {
	margin:0;
	padding:0;
}

#tag-list-container li a {
    margin-bottom: 1em;
    padding: 0.5em 1.25em;
    display: block;
    float: left;
    margin-right: 1em;
    border-radius: 6em;
		font-size:var(--size-p-small);
		line-height:1.7em;
}

/* Footer */

.entry-footer {
	display:none;
}

/* ===== Footer card ========================================================
   The site's closing object: the same dark card the stack deals, laid down
   one last time. It flows into view at the end of the page like any other
   element. It starts LIGHT — #fff on the mono-6 page ground, exactly like a
   stack card, a riser card or a carousel card — and as the reader passes
   halfway down it, eight ink slats sweep from the top edge and it becomes the
   dark closing object. The last card in the deck, turning into the end of the
   page.

   WHY THIS IS SAFE WHERE THE 3.19-3.20 FOOTER WAS NOT. That one was a fixed
   card over the viewport, so the ink travelled across LIVE CONTENT and needed
   two scroll invariants (the article must be gone before the ink is solid;
   the runway must start below the fold) that had to hold under every resize,
   zoom and reflow. Here the ink never leaves the card: the slats are clipped
   to .footer-ink, which is inset:0 inside a card that is in normal flow.
   There is nothing behind it to cover, so neither invariant exists. What
   survives from that work is the stagger floor, which is about whether the
   thing reads as slats at all, and applies to any sweep anywhere.

   AND IT ESCAPES THE EXCHANGE-RATE TRAP STRUCTURALLY. That rule bit because
   the old card's frame was STATIONARY while the content behind it departed at
   scroll speed, so a linear build sat at a fixed offset from the departing
   edge and moved in lockstep with it — perfect coverage, no visible motion.
   Here the frame is the card, and the card's contents move WITH the card.
   The eye reads the ink edge against the card's own edge, and that relative
   motion is H/D per pixel of scroll: nonzero for any finite runway, with no
   currency to trade. There is no departing content to lock step with.

   THE TEXT INVERTS AT THE INK EDGE, which is the point — a theme flip, not a
   background change. footer.php renders the plate twice, identical markup
   under identical CSS so the two lay out to the same pixels; the dark copy
   lives in .footer-ink and is revealed by the same clip-path that draws the
   ink. A glyph the edge passes through is dark above and light below.
   ========================================================================= */

#colophon {
  /* the two numbers the framing is built from — the same values .card-stack
     declares, so the page's first card and its last one share a frame */
  --foot-inset: 1vh;      /* gap under the nav, matched at the other 3 edges */
  --foot-radius: 1em;     /* one radius for this card and for the stack's */
  display: block;
  margin: var(--space-section) 0 0;
  padding: 0;
  /* the card carries the ground now, not the footer element */
  background: transparent;
  /* NO .theme-dark here any more — the footer is light at rest and only the
     .footer-ink subtree is dark. Every rule below is written against tokens,
     so the SAME declaration resolves correctly in both copies of the plate:
     `color: var(--color-ink)` is mono-1 in the light one and mono-6 in the
     dark one, with no second rule to keep in step. */
  color: var(--color-ink);
}

.footer-card {
  /* positioned: .footer-ink is inset:0 against this. (It briefly wasn't —
     the property was removed at 3.20.8 once the old slat layer was gone, and
     it is back now for the same reason it existed then.) */
  position: relative;
  /* a flex column so the plate fills the card's height rather than only its
     own: without it `height: 100%` on the plate resolves to auto against an
     auto-height parent and the small print stops sitting on the floor. */
  display: flex;
  flex-direction: column;
  overflow: hidden;   /* the radius crops the corners */
  border-radius: var(--foot-radius);
  /* A font floor for the whole card. .footer-copyright declares no family and
     neither did any ancestor, and site.css has no body{font-family} — so the
     chain ran all the way out to style.css's system stack and the small print
     rendered in Segoe UI / SF Pro next to its Archivo siblings (.footer-say h2
     and .footer-nav li both set a family; the copyright's children are bare
     inline nodes wpautop leaves without a <p> for element rules to match). */
  font-family: var(--font-body);
  /* One step DARKER than the page, not lighter. --color-surface-2 is mono-5
     (#f2f2f2) against the mono-6 (#fafafa) ground — the token's own role is
     "sunken / subtle fill", which is exactly right here: the closing card sits
     INTO the page rather than on it, and then fills with ink.
     Deliberately NOT --color-surface, which in the light theme IS the page
     ground, so a card painted with it would have no edge at all. And not the
     #fff the stack/riser/carousel cards use — those are raised objects dealt
     over the page; this one is a recess in it.
     8 levels of separation is a whisper, and that is the intent. The dark
     ground arrives with .footer-ink, which is in a .theme-dark scope. */
  background: var(--color-surface-2);
  /* Horizontal margins are --page-gutter, NOT --foot-inset (2026-08-30): the
     UI margin rule aligns the footer with the Kapta logo's left margin and
     mirrors it right — the same gutter-to-gutter frame the megamenu panel
     spans (nav.css --knav-panel-w). --foot-inset keeps only the bottom gap,
     its original job of framing the card off the viewport floor. This is a
     deliberate departure from .card-stack's 1vh side insets. */
  margin: 0 var(--page-gutter, 3rem) var(--foot-inset);
  /* THIS NUMBER DOES THREE JOBS: it is the card's height, the ink's travel (H)
     and, because the trigger spans the card's own height and the sweep runs
     across its second half, the runway (H/2). Change it and all three rescale
     together — which is the point of tying them to one number. At a near-full
     viewport the sweep now gets about half a screen of scroll, where at 50vh
     it had a quarter: the same effect, twice as much room to read.

     NO CONTENT FLOOR. This took three tries to get right, and every failed
     attempt kept the same mistake: the card was being sized by its CONTENT,
     and each "fix" preserved the floor that let it. `min-height: 50vh` only
     binds when it exceeds the intrinsic height. `min-height: max-content`
     binds ALWAYS — it is the intrinsic height, so it beat the 50vh outright
     and was strictly worse than what it replaced. A definite height with any
     content-based minimum next to it is not a definite height.

     So: `height` alone. The plate is a grid whose second row is
     `minmax(0, 1fr)`, so it takes whatever the card gives it and its own
     content can compress into that — nothing here needs the card to grow.

     The clipping risk that justified the floor is real but narrow: it only
     bites when 50vh falls below what the plate can compress to, which is a
     SHORT viewport, not a narrow one. So it is handled by a height query
     below rather than by a floor that costs the height everywhere else.

     vh rather than svh: svh is the height that does not change when a mobile
     URL bar collapses, so it cannot resize the card mid-scroll and shift the
     trigger's measurements under it — but that only ever mattered between
     768px and tablet widths, and on desktop the two are identical.

     THE VALUE: nearly the whole viewport, so that at maximum scroll the card's
     top edge lands just under the fixed nav. Derived, not guessed —
     --knav-h (4.75rem) is the bar's own height token from nav.css, and the two
     insets are the gap under the nav and the gutter below the card. Written
     this way the card re-fits itself if the bar's height ever changes. */
  height: calc(100vh - var(--knav-h, 4.75rem) - var(--foot-inset) * 2);
}

/* The clipping guard, scoped to the case that actually needs it: a SHORT
   viewport, not a narrow one. Below this the plate cannot compress into the
   card without cropping, so the card goes back to being content-sized — and
   the sweep, whose travel and runway are both this height, scales with it. */
@media (max-height: 520px) {
  .footer-card { height: auto; }
}

/* ---- The ink layer ----------------------------------------------------- */

/* Absolutely positioned over the card, in a dark scope, holding the second
   copy of the plate. Revealed by clip-path: a SINGLE polygon tracing all
   eight column edges, not eight elements and not eight mask layers.
   That choice is load-bearing: one filled polygon CANNOT have a seam between
   columns, so the -1px overlap every other slat implementation needs (flex
   sub-pixel rounding) has no equivalent problem here, and there is no way for
   a hairline of light plate to show through the dark one.

   Defaults to FULLY DARK (--c* : 100). With no JS, no GSAP or reduced motion
   the reader gets the finished dark card — the known-good state — and the
   script's first act is to park it at 0 and animate from there. The failure
   direction matters: an effect that fails to "unstyled light card with a
   white logo on it" would be a broken page, and this cannot reach that. */
.footer-ink {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  /* the real plate underneath always takes the pointer; hover on the visible
     dark copy is mirrored onto it by scripts.js */
  pointer-events: none;
  background: var(--color-surface);   /* .theme-dark → ≈#101010 */
  /* Percentages, not bare numbers with a calc() in the polygon. GSAP records
     the unit it finds on a custom property and writes it back each frame; a
     unitless value it decided to call `px` would make every coordinate
     `calc(100px * 1%)`, which is invalid, and an invalid clip-path is simply
     dropped — leaving the ink permanently covering the card. Declaring the
     unit here means there is nothing to infer. */
  --c0: 100%; --c1: 100%; --c2: 100%; --c3: 100%;
  --c4: 100%; --c5: 100%; --c6: 100%; --c7: 100%;
  /* One polygon down the right side, then a staircase back along the eight
     column edges. Column i's bottom is var(--c<i>); i=0 is leftmost. */
  clip-path: polygon(
    0 0, 100% 0,
    100%   var(--c7), 87.5% var(--c7),
    87.5%  var(--c6), 75%   var(--c6),
    75%    var(--c5), 62.5% var(--c5),
    62.5%  var(--c4), 50%   var(--c4),
    50%    var(--c3), 37.5% var(--c3),
    37.5%  var(--c2), 25%   var(--c2),
    25%    var(--c1), 12.5% var(--c1),
    12.5%  var(--c0), 0     var(--c0)
  );
}

/* The white mark is correct on the dark copy and invisible on the light one.
   invert(1) on a white-on-transparent PNG gives black-on-transparent — alpha
   is untouched. A real black mark asset would be better than a filter; there
   isn't one in /designsystem/assets/ yet. */
.footer-plate-light .footer-mark img { filter: invert(1); }

/* Hover/focus mirrored from the real link onto its dark twin by index — a
   pointer can only be over one copy, and the visible one is whichever the ink
   says. Carries the ID for the same reason every other rule in here does. */
#colophon .footer-ink a.is-hover::after { transform: scaleX(1); }

/* No border and no shadow, unlike .stack-card. The stack's cards are dealt
   over each other and need an edge to separate them; this one is the last
   thing on the page with nothing behind it, so the ground is the edge. */

.footer-plate {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto minmax(0, 1fr);
  gap: var(--space-5) clamp(2rem, 7vw, 7rem);
  padding: clamp(1.75rem, 4vw, 3.5rem) clamp(1.75rem, 5vw, 5rem);
  text-align: left;
}

.footer-mark { grid-column: 1 / -1; }

.footer-mark img {
  display: block;
  width: 75px;
  height: auto;
  margin: 0;
}

/* Column 1: the invitation at the top, the small print pinned to the floor.
   margin-top:auto on .footer-copyright rather than justify-content:space-
   between, because this column is filled by the_content and wpautop can leave
   a stray paragraph in here that would silently take a slot of its own. */
.footer-content {
  grid-column: 1;
  grid-row: 2;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.footer-content p { margin: 0; }

.footer-say h2 {
  margin: 0;
  font-family: var(--font-display);
  /* DS subtitle role, as the menu curtain uses — 600 SemiExpanded */
  font-weight: var(--subtitle-weight);
  font-stretch: var(--subtitle-stretch);
  font-size: clamp(2.25rem, 5.2vw, 4.5rem);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--color-ink);
}

/* .footer-copyright, not #copyright: the plate is rendered twice, so an id in
   here would ship duplicated and invalidate the document. Renamed 2026-08-30
   along with content/pages/footer.html. Nothing in JS referenced the id, and
   update_copyright_year() matches on `2016-\d{4}` in the content, not on it. */
.footer-copyright {
  margin: var(--space-5) 0 0;
  margin-top: auto;
  padding: 0;
  width: auto;
  text-align: left;
  font-size: var(--text-sm);
  color: var(--color-ink-3);
}

.footer-copyright a { margin-left: var(--space-4); }

.footer-nav {
  grid-column: 2;
  grid-row: 2;
  align-self: start;
}

.footer-nav ul {
  display: flex;
  flex-direction: column;
  align-items: flex-start;   /* each link only as wide as its own text */
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer-nav li {
  margin: 0;
  padding: 0;
  font-family: var(--font-display);
  font-weight: var(--subtitle-weight);
  font-stretch: var(--subtitle-stretch);
  font-size: var(--text-xl);
  line-height: var(--leading-snug);
}

/* DS link, on a dark ground: inherits the ink, no rest underline, hover
   draws a currentColor rule left → right. Same recipe as #menu a — the two
   lists share a source file, so they share a link style.

   KEYED ON THE ID, not on `footer a`, and that is load-bearing: the legacy
   base rule up top is `a, a:hover { color: var(--color-black) }`, and
   `a:hover` (0,1,1) outranks `footer a` (0,0,2). Element-scoped, every link
   in here turned #050505 ON HOVER — near-black on a #101010 card, taking the
   currentColor underline invisible with it, so the link appeared to vanish
   under the cursor. `#colophon a` is (1,0,1) and clears it. This is why
   #menu a was written with the ID too; the pattern is not decoration.
   The `footer a` entry in the global link exclusion list still applies —
   these elements are still `footer a`, whatever we key our own rule on. */
#colophon a {
  position: relative;
  display: inline-block;
  color: var(--color-ink);   /* mono-6 #fafafa on #101010 — 18.2:1 */
  text-decoration: none;
}

/* No hover COLOUR change, by design: the rest ink is mono-6, the top of the
   scale, so there is nowhere lighter to go — the same trap the Palveluitamme
   pills hit. The drawn rule carries the hover instead, exactly as in the
   menu curtain. */

#colophon a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.08em;
  height: max(2px, 0.075em);   /* DS in-content link weight */
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--motion-slow, 0.28s) var(--ease-standard, ease);
}

/* The nav list and the invitation are UI elements, not prose: the heavier
   DS "UI element link" rule, floored at 4px so it holds at display scale.
   Carries the ID too — without it (0,1,2) loses to #colophon a::after
   (1,0,2) and the 4px never lands. */
#colophon .footer-nav a::after,
#colophon .footer-say a::after { height: max(4px, 0.075em); }

#colophon a:hover::after,
#colophon a:focus-visible::after { transform: scaleX(1); }

@media (max-width: 767.98px) {
  /* 768 was originally the ARM CUTOFF — below it the card never animated, so
     it was styled static here. Reviewed at 3.20.8 against the DS content
     handoff (--bp-content 992px) and DELIBERATELY KEPT, now on a layout
     reason of its own: the nav column is three short words, so the two-column
     plate holds all the way down to 768 and collapsing it at 992 would throw
     away a band of tablet widths where the card still reads as a card. This
     is the one place the footer is allowed to disagree with --bp-content;
     if the nav grows past ~5 items, re-check it here first.

     Below the breakpoint the plate is a single column and the card grows to
     its contents — min-height is dropped rather than reduced, because on a
     phone the content IS the height. */
  .footer-plate {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto;
  }
  .footer-mark,
  .footer-content,
  .footer-nav {
    grid-column: 1;
    grid-row: auto;
  }
  /* single column, so the card is purely content-sized — a viewport-fraction
     height on a phone would either crop the stacked plate or leave a field of
     empty ink under it */
  .footer-card { height: auto; min-height: 0; }
  .footer-copyright { margin-top: var(--space-5); }
}

/* ===== Components ===== */

/* Scroll up */

.scroll-up {
  width: 45px;
  height: 45px;
  background: black;
  border-radius: 50%;
  padding: 6px;
  /* Pulled in from the old 15px (2026-08-30). The footer card is now nearly
     the full viewport, so at the end of the page this button always sits ON
     it — and the card's gutter is --foot-inset, 1vh, which is only ~9px. At
     15px the button cleared the card's own edge by about six pixels and read
     as jammed into the corner. --space-5 puts a clear ~23px of card between
     the button and both edges.
     A literal token, not calc() off --foot-inset: that property is declared on
     #colophon and this element is a fixed child of <body>, so it is not in
     scope here and the var would silently fall back. */
  right: var(--space-5);
  bottom: var(--space-5);
  position: fixed;
  z-index: 9;
  cursor: pointer;
  display: block;
	animation-duration: 1.5s;
  animation-iteration-count: 1;
  transform-origin: bottom;
	transition: background 0.25s ease-in-out 25ms;
	display:none;
}

.scroll-up:hover { 
	background: #eee;
	transition: background 0.25s ease-in-out 25ms;
}

.scroll-up path {
	fill:#fff;
	transition: 0.25s ease-in-out 25ms;
}

.scroll-up:hover path {
	fill:#000;
}
	
/* Reveal content on scroll */

.content-wrapper {
  position: relative;
  transform: translateY(150px);
  opacity: 0;
  transition: 1s all ease;
}

.content-wrapper.active {
  transform: translateY(0);
  opacity: 1;
}

/* Lightbox */

#lightbox {
    display: none; 
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5); 
    justify-content: center;
    align-items: center;
    z-index: 1000;
		overflow:auto;
}

#lightbox.show {
  display: flex;
  opacity: 1;
}

#lightbox-content {
  width: fit-content;
  background: white;
  border-radius: var(--radius-medium);
  padding: 4rem;
	padding-top: 1rem;
  position: relative;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
	margin:0 auto;
	margin-top:10vh;
	margin-bottom:10vh;
}

#close-lightbox {
  position: absolute;
  top: 0.5em;
  right: 0.5em;
  background: none;
  border: none;
  font-size: 1.5em;
  line-height: 1;
  cursor: pointer;
}

#lightbox-inner-content {
  margin-top: 1em;
}

/* Contact form */

.kform {
	margin-top: var(--space-4);
	max-width: 28rem;
}

.kform p {
	margin: 0 0 var(--space-3);
	font-size: var(--text-base);
}

.kform label {
	display: block;
	font-family: var(--font-display);
	font-weight: var(--weight-medium);
	font-size: var(--text-sm);
	margin-bottom: var(--space-1);
}

.kform input,
.kform textarea {
	width: 100%;
	box-sizing: border-box;
	font: inherit;
	font-size: var(--text-base);
	color: var(--mono-1);
	background: #fff;
	border: 1px solid var(--mono-4);
	border-radius: var(--radius-md);
	padding: 0.6em 0.9em;
	transition: border-color var(--motion-fast, 150ms) ease;
}

.kform input:focus,
.kform textarea:focus {
	border-color: var(--green-3);
}

.kform-hp {
	position: absolute !important;
	left: -9999px;
	top: 0;
}

.kform-privacy {
	font-size: var(--text-sm);
	color: var(--mono-3);
}

.kform-submit {
	display: inline-flex;
	align-items: center;
	font-family: var(--font-display);
	font-weight: var(--weight-medium);
	font-size: var(--text-base);
	background: var(--mono-1);
	color: var(--mono-6);
	border: 0;
	border-radius: var(--radius-full);
	padding: 0.7rem 1.6rem;
	cursor: pointer;
	transition: background var(--motion-medium, 250ms) ease;
}

.kform-submit:hover:not(:disabled) { background: var(--mono-2); }
.kform-submit:disabled { opacity: var(--disabled-opacity); cursor: not-allowed; }

.kform-status {
	min-height: 1.5em;
	font-size: var(--text-sm);
}

/* Buttons — DS primary: filled mono-1 pill, Archivo Medium; hover widens + arrow-right slides in */

.btn {
	margin:0 auto;
	padding:0.5em 1.5em;
	border-radius: var(--radius-full);
	cursor:pointer;
	font-family: var(--font-display);
	font-weight: var(--weight-medium);
	font-size: var(--text-base);
	text-decoration:none;
	margin-right:2em;
	transition: background-color var(--motion-fast) ease, color var(--motion-fast) ease, border-color var(--motion-fast) ease;
}

.btn-centered {
	margin:0 auto;
	padding:12px 25px;
	border-radius: var(--radius-full);
	cursor:pointer;
	font-family: var(--font-display);
	font-weight: var(--weight-medium);
	font-size: var(--text-base);
	position:absolute;
	left: 50%;
  transform: translateX(-50%);
	text-decoration:none;
	transition: background-color var(--motion-fast) ease, color var(--motion-fast) ease, border-color var(--motion-fast) ease;
}

/* Widen-and-reveal arrow — DS: only primary + call-to-action carry the arrow. */

.btn-black::after,
.btn-primary::after,
.btn-cta::after {
	content:"";
	display:inline-block;
	width:0;
	height:1em;
	margin-left:0;
	vertical-align:-0.15em;
	background-color: currentColor;
	-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E") no-repeat center / contain;
	mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E") no-repeat center / contain;
	transition: width var(--motion-slow) var(--ease-standard), margin-left var(--motion-slow) var(--ease-standard);
}

.btn-black:hover::after,        .btn-black:focus-visible::after,
.btn-primary:hover::after,      .btn-primary:focus-visible::after,
.btn-cta:hover::after,          .btn-cta:focus-visible::after {
	width:1em;
	margin-left:0.5em;
}

/* Primary — grayscale, theme-independent (.btn-black is the shipped alias) */

.btn-primary,
.btn-black {
	border:1px solid var(--mono-1);
	background: var(--mono-1);
	color: var(--mono-6);
}

/* DS call-to-action button — accent green, highest emphasis */
.btn-cta {
	border:1px solid var(--color-accent);
	background: var(--color-accent);
	color: var(--color-accent-contrast);
}

.btn-cta:hover {
	background: var(--color-accent-hover);
	border-color: var(--color-accent-hover);
	color: var(--color-accent-contrast);
}

.btn-primary:hover, .btn-primary:focus-visible,
.btn-black:hover, .btn-black:focus-visible {
	background: var(--mono-2);
	border-color: var(--mono-2);
	color: var(--mono-6);
}

.btn-primary:active,
.btn-black:active {
	background: var(--mono-1);
	border-color: var(--mono-1);
}

/* Call-to-action — brand green, highest emphasis */

.btn-cta {
	border:1px solid var(--color-accent);
	background: var(--color-accent);
	color: var(--color-accent-contrast);
}

.btn-cta:hover, .btn-cta:focus-visible {
	background: var(--color-accent-hover);
	border-color: var(--color-accent-hover);
}

.btn-cta:active {
	background: var(--color-accent-active);
	border-color: var(--color-accent-active);
}

/* Secondary — outline, low emphasis; no arrow */

.btn-secondary {
	border:1px solid var(--color-border-strong);
	background: transparent;
	color: var(--color-ink);
}

.btn-secondary:hover, .btn-secondary:focus-visible {
	background: var(--color-surface-2);
	border-color: var(--color-ink-3);
}

/* Warning — orange, caution; no arrow */

.btn-warning {
	border:1px solid var(--color-warning);
	background: var(--color-warning);
	color: var(--mono-1);
}

.btn-warning:hover, .btn-warning:focus-visible {
	background: color-mix(in srgb, var(--color-warning) 86%, #000);
	border-color: color-mix(in srgb, var(--color-warning) 86%, #000);
}

/* Disabled — DS state rules */

.btn:disabled,
.btn[aria-disabled="true"],
.btn-centered:disabled,
.btn-centered[aria-disabled="true"] {
	opacity: var(--disabled-opacity);
	cursor: not-allowed;
	pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
	.btn, .btn-centered, .btn::after, .btn-centered::after {
		transition: none;
	}
}

/* Accordion */

.acc-set i {
	font-style:normal !important;
}

.acc-container {
  position: relative;
  max-width: 100%;
  height: auto;
  margin: auto;
}

.acc-set {
  position: relative;
  width: 100%;
  height: auto;
}

.acc-set > a {
  display: block;
  padding: 1em 0;
  text-decoration: none;
  color: #777;
  font-weight: 600;
  border-bottom: 1px solid #ddd;
	cursor:pointer;
	font-size:1.3em;
}

.acc-set > a i {
  float: right;
  margin-top: 2px;
}

.acc-set > a.active {
  color: #162236;
	font-weight: 600;
}

.acc-set:last-child a {
	
}

.acc-content {
  bottom: none;
  display:none;
  overflow: hidden; 
}

.acc-content p {
  padding: 1em 0 1em 0;
  margin: 0;
  color: #333;
	opacity:0;
}

.acc-extras {
    display: inline-block;
    position: relative;
    width: 20px;  /* Set a fixed width for the icon */
    height: 20px; /* Set a fixed height for the icon */
    font-size: inherit;
    text-rendering: auto;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    display: flex;
    align-items: center;
    justify-content: center;
}

.acc-extras:before {
    content: "+";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%); /* Center the plus sign */
    transition: opacity 0.4s ease; /* Smooth opacity transition */
    opacity: 1; /* Plus is initially visible */
}

.acc-extras:after {
    content: "–";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%); /* Center the minus sign */
    transition: opacity 0.4s ease; /* Smooth opacity transition */
    opacity: 0; /* Minus is initially hidden */
}

/* When the accordion is active (open), show minus and hide plus */
.acc-set > a.active .acc-extras:before {
    opacity: 0; /* Fade out the plus sign */
}

.acc-set > a.active .acc-extras:after {
    opacity: 1; /* Fade in the minus sign */
}

.acc-container .btn {
    display: inline-block;
    margin-top: 2em;
    margin-bottom: 2em;
}

/* ===== Responsiveness ===== */

/* Media queries */

/* Small devices specific rules (phones, up to 992px) */
@media screen and (min-width: 0px) and (max-width: 991.98px) {
	h2 {
	font-size: var(--text-xl);
	}
	h3 {
	font-size: var(--text-lg);
	}
	h4 {
	font-size: var(--text-xs);
	}
	p {
	font-size: var(--text-base);
	}
	.heading-h2:not(footer .heading-h2) {
	text-align:left;
	}
	.heading-h3:not(footer .heading-h3) {
	text-align:left;
	}
	.heading-h4 {
	text-align:left;
	}
	.col-25, .col-50, .col-75 {
		flex: 1 1 100%;
	}
	.m-r-medium {
	margin-right: 0;
	}
	.btn {
	display:block;
	margin-top:1rem;
	}
	.btn-centered {
	display:block;
	margin-top:1rem;
	position:relative;
	text-align:center;
  left: 0%;
  transform: translateX(0%);
	max-width:75vw;
	margin:0;
	}
	figure img {
	width:100%;
	border-radius:0.5em;
	}
	/* no !important: this block is (0,1,0) and comes after the 1400px one, so
	   it already wins on order. The keyword was covering for nothing. */
	#menu li {
	font-size: var(--text-xl);
	padding:0;
	margin-top:0;
	}
	/* Mobile: no hero (substitute comes later) — hide it entirely and give
	   the content a normal top clearance under the fixed nav */
	body.home .hero-riser {
	height: auto;
	padding: 1em 1.5em;
	}
	body.home .hero-riser section {
	flex-wrap: wrap;
	gap: 1em;
	}
	.content-wrapper-white {
	padding:0em;
	background:none;
	}
	.acc-set > a {
	font-size:1em;	
	}
	.acc-content p {
	font-size:1rem;
	}
	.acc-extras {
	font-style:normal;
	}
	#lightbox-content {
	max-width:90vw;
	padding:1.5rem;
	padding-bottom:0.25rem;
	padding-top:0.25rem;
	margin-top:2rem;
	}
	#lightbox-content li {
	font-size:1rem;
	}
	.kform {
	margin-top: var(--space-3);
	max-width: 100%;
	}
	.kform-submit {
	font-size: 1rem;
	width: 100%;
	justify-content: center;
	}
	.info-container {
	padding:0;
	padding-top:0.5em;
	border-top:1px solid var(--color-gray);
	border-radius:0;
	background:none;
	}
	.single-post .entry-content, .single-palvelu .entry-content {
	margin-top:200px;
	padding-top: var(--margin-large);
	}
	.single-palvelu .colorcontainer, .single-post .colorcontainer {
	margin-bottom: var(--space-5);
	}
	#tag-list-container {
	padding: var(--space-section) 0;
	}
	.hero-text-container {
	margin-top: calc(var(--space-section) * 1.25);
	margin-bottom: calc(var(--space-section) * 1.75);
	}
	.page .entry-content {
	padding-bottom: var(--space-5);
	}
	.page-top {
	margin-top: var(--space-section);
	}
	.heading-h2 + p {
	margin-top:0;
	}
	.btn-centered + .notification-container {
	margin-top: var(--space-5);
	}
	.colorcontainer.staff-card {
	margin-top: var(--space-5);
	}
	.colorcontainer.content-wrapper-white {
	margin-top: var(--space-5);
	}
	.heading-h2 + .wp-block-columns {
	margin-top:0;
	}
	.content-wrapper .acc-container {
	margin-top: var(--space-5);
	}
	.single-post .meta, .single-post #random-banner, .single-palvelu .meta, .single-palvelu #random-banner{
	display:none;
	}
	.single-post .content-left ul {
		width:100%;
	}
	.single-post .content-left li {
		display:inline;
		margin-right:5px;
	}
	.single-post .content-right h1, .single-post .content-right h2 {
		font-size:1.5em;
	}
	.single-post .content-right p {
		font-size:1.25em;
		line-height:1.5em;
	}
	.single-post .content-right blockquote, .single-post .content-right blockquote p {
		font-size:1.5em;
		margin-left:0;
		margin-right:0;
	}
	/* Still hidden below 992px, but the REASON is now historical: the old
	   hero was a fixed 800x600 masked object that could not fit a phone, and
	   the `width: 200vw` here was part of that fight (dead under display:none
	   — dropped). The band is fluid and would work at any width, so this is
	   now a deliberate choice not to ship a video download to phones rather
	   than a layout necessity. Delete the rule to turn it on. */
	#hero-palvelumme-container, #hero-esittely-container {
	display:none;
	}
	.hero-text-container {
	padding-right:0;
	}
	.acc-container .btn {
	width:100%;
	max-width:100%;
	margin-top:1em;
	margin-bottom:0;
	}
	/* footer::before (the white radius shelf) is gone — the footer card
	   carries its own --foot-radius on every edge now */
}

/* Phones and tablets up to --bp-nav (1400px) — the band where the masthead is
   a hamburger. Two sibling blocks were deleted here on 2026-08-30: an empty
   992–1400 query containing only `header {}`, and an empty min-width:1700
   query containing only `h1 {}`. Neither had ever carried a declaration. */
@media screen and (min-width: 0px) and (max-width: 1400px) {
	h3 {
		padding: 0;
	}
	.scroll-up {
		display: none;
	}
	/* the overlay carries the service groups and the CTA now, so the top-level
	   links give up a step of the scale to make room for them */
	#menu li {
		font-size: var(--text-2xl);
	}
	#lightbox {
		padding:2vw;
	}
	.entry-content {
		max-width:100%;
	}
	.random-banner-element {
		max-width:90vw;
}
	/* `display:none:` — a colon for a semicolon, so the declaration was invalid
	   and dropped, leaving an empty rule. Left as a no-op rather than
	   "corrected", because the selector is a CLASS and every page uses the ID
	   (#hero-esittely-container), which this block does not match: making it
	   valid would change nothing, and making it an ID would newly hide the
	   esittely hero all the way up to 1400px, which nobody asked for.
	.hero-esittely-container { display: none; } */
	.random-banner-video {
		max-width:90vw;
}
}



/* Consent banner (consent.js) — dormant until a GA ID is configured */

.kconsent {
	position: fixed;
	left: var(--space-3);
	right: var(--space-3);
	bottom: var(--space-3);
	z-index: var(--z-modal);
	max-width: 34rem;
	margin-inline: auto;
	background: var(--color-surface);
	color: var(--color-ink);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-lg);
	padding: var(--space-4);
	font-size: var(--text-sm);
	line-height: var(--leading-normal);
}

.kconsent p { margin: 0 0 var(--space-3); }

.kconsent-actions { display: flex; gap: var(--space-2); }

.kconsent-actions button {
	flex: 1;
	font-family: var(--font-display);
	font-weight: var(--weight-medium);
	font-size: var(--text-sm);
	border-radius: var(--radius-full);
	padding: 0.55rem 1.2rem;
	cursor: pointer;
	transition: background var(--motion-fast) ease;
}

.kconsent-accept {
	background: var(--mono-1);
	color: var(--mono-6);
	border: 1px solid var(--mono-1);
}
.kconsent-accept:hover { background: var(--mono-2); }

.kconsent-decline {
	background: transparent;
	color: var(--color-ink);
	border: 1px solid var(--color-border-strong);
}
.kconsent-decline:hover { background: var(--color-surface-2); }
