/* Kapta.fi navigation (.knav). Consumes /designsystem/tokens.css. */

:root {
  /* THE UI MARGIN RULE (2026-08-30): --page-gutter is the margin extreme for
     every UI element except the hero and deliberately full-bleed bands (the
     customer ribbon). The megamenu panel therefore spans gutter to gutter —
     the Kapta logo's own left margin, mirrored on the right — and the footer
     card does the same (site.css .footer-card). A 1535px content-width cap
     lived here for one release (3.27.3–3.27.4) and was rolled back to this.

     --knav-panel-w stays a separate variable even though it currently equals
     --knav-w: the panel, the sizer and nav.js's measure() all read it, so if
     the panel ever diverges from the bar again it is one line, and the
     measured and rendered widths cannot disagree. */
  --knav-w: calc(100vw - 2 * var(--page-gutter, 3rem));   /* full-width mode */
  --knav-panel-w: var(--knav-w);
  --knav-h: 4.75rem;
}

/* ---- Bar ---- */
.knav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 90;
  height: var(--knav-h);
  padding: var(--space-2) 0;
  color: var(--mono-1);
  /* Glassmorphism */
  background: color-mix(in srgb, #fff 65%, transparent);
  -webkit-backdrop-filter: saturate(140%) blur(8px);
  backdrop-filter: saturate(140%) blur(8px);
  box-shadow: 0 1px 0 color-mix(in srgb, var(--mono-1) 6%, transparent);
  transition: background .25s ease, box-shadow .25s ease;
}
/* No hover state on the bar (removed 3.17.64): the glass is the standing
   state and it stays glass — only opening a panel makes it solid. */
.knav.is-open { background: #fff; box-shadow: none; }

.knav-row {
  display: flex; align-items: center; gap: var(--space-3);
  height: 100%;
  max-width: var(--knav-w);
  margin: 0 auto;
}

.knav-brand { display: inline-flex; align-items: center; color: inherit; text-decoration: none; margin-right: var(--space-3); }
.knav-brand img { height: 1.15rem; width: auto; display: block; }

.knav-items { display: flex; align-items: center; gap: var(--space-1); flex: 1; }

.knav-item {
  font-family: var(--font-display);
  font-weight: var(--subtitle-weight);
  font-stretch: var(--subtitle-stretch);
  font-size: var(--text-sm);
  line-height: 1;
  color: inherit; background: none; border: 0; cursor: pointer;
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  text-decoration: none; white-space: nowrap;
  transition: color .15s ease;
}
/* .is-dismissed = clicked shut with the pointer still on the item. A click
   does not end :hover, so without this the item held the open look — green,
   chevron up — over a panel that had already retracted. nav.js clears it on
   mouseleave, so the opt-out lasts exactly as long as the stuck hover does. */
.knav-item:hover:not(.is-dismissed), .knav-item.is-active { color: var(--green-3); }

/* Chevron */
.knav-chev { position: relative; width: 0.95rem; height: 0.95rem; display: block; transform: translateY(-1px); }
.knav-chev svg { display: block; position: absolute; inset: 0; width: 100%; height: 100%; transition: opacity .15s ease; }
.knav-chev .chev-down { color: var(--mono-1); opacity: 1; }
.knav-chev .chev-up { color: var(--green-3); opacity: 0; }
.knav-item:hover:not(.is-dismissed) .chev-down, .knav-item.is-active .chev-down { opacity: 0; }
.knav-item:hover:not(.is-dismissed) .chev-up, .knav-item.is-active .chev-up { opacity: 1; }

/* CTA */
.knav-cta { display: flex; align-items: center; }
.knav-btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-weight: var(--weight-medium);
  font-size: var(--text-sm);
  background: var(--mono-1); color: var(--mono-6);
  border: 0; border-radius: var(--radius-full);
  padding: 0.5rem 1.1rem; cursor: pointer; white-space: nowrap;
  transition: background .2s ease, padding .28s cubic-bezier(.4,0,.2,1);
}
.knav-btn .btn-arrow {
  display: inline-flex; align-items: center;
  width: 0; margin-left: 0; opacity: 0; overflow: hidden;
  transition: width .28s cubic-bezier(.4,0,.2,1), margin .28s cubic-bezier(.4,0,.2,1), opacity .2s ease;
}
.knav-btn .btn-arrow svg { width: 1.15em; height: 1.15em; display: block; }
.knav-btn:hover, .knav-btn:focus-visible { background: var(--mono-2); padding-right: 0.85rem; }
.knav-btn:hover .btn-arrow, .knav-btn:focus-visible .btn-arrow { width: 1.15em; margin-left: .5rem; opacity: 1; }

/* Hamburger: a flex child of .knav-row (layout + icon in site.css) */

/* ---- Mega-panel ----
   THE SLAT CURTAIN, the same language as the hamburger overlay (site.css,
   .menu-slats) and the hero: vertical strips dropped from above, sweeping
   left → right, and the strips ARE the background rather than something drawn
   over one. Hence `background: none` on the panel itself.

   Timings are the hamburger's, tightened, because this one opens on HOVER and
   the overlay's ~0.6s sweep would read as lag on a pointer. 8 slats at .022s
   apart means the last one starts at .154s and lands at .394s.

   NOT reused for a panel-to-panel switch: the curtain drops once, and moving
   between open panels is the content sliding behind a curtain that is already
   down. openFor() only runs the sweep when the panel was closed. */
:root { --knav-slat-dur: .24s; --knav-slat-step: .022s; }

.knav-panel {
  position: absolute;
  top: var(--knav-h); left: 50%;
  width: var(--knav-panel-w);
  height: 0;
  background: none;              /* the slats are the background */
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  overflow: hidden;              /* clips the slats while they are still up */
  transform: translateX(-50%);
  pointer-events: none;
  /* height 0s .42s — the CLOSED value, so collapsing the box waits out the
     slat retraction instead of guillotining it. close() can therefore set
     height 0 immediately and let CSS hold it. Compact and light shadow: the
     scrim already separates the panel from the page, so it only seats the
     bottom edge (~8px reach at .12; was 0 20px 30px -12px / .20). */
  box-shadow: 0 0 0 rgba(5,5,5,0);
  transition: height 0s .42s,
              width .28s cubic-bezier(.4,0,.2,1),
              box-shadow .2s ease;
}
.knav-panel.is-open {
  pointer-events: auto;
  box-shadow: 0 10px 16px -10px rgba(5,5,5,.12);
  /* height animates again HERE, for the switch between two open panels; the
     first open sets it inline with transition:none so the slats have a full
     box to fall into. */
  transition: height .28s cubic-bezier(.4,0,.2,1),
              width .28s cubic-bezier(.4,0,.2,1),
              box-shadow .2s ease .18s;
}

.knav-slats { position: absolute; inset: 0; display: flex; z-index: 0; pointer-events: none; }
.knav-slats i {
  flex: 1 1 0;
  background: #fff;
  border-top: 1px solid var(--mono-5);   /* was on the panel; rides the slat in */
  transform: translateY(-101%);
  transition: transform var(--knav-slat-dur) var(--ease-standard, cubic-bezier(.4,0,.2,1));
  transition-delay: calc((7 - var(--i)) * var(--knav-slat-step));   /* closing: right → left */
}
/* overlap by a pixel — flex sub-pixel rounding opens seams otherwise
   (learned on the hamburger curtain, .menu-slats i + i) */
.knav-slats i + i { margin-left: -1px; }
.knav-panel.is-open .knav-slats i {
  transform: translateY(0);
  transition-delay: calc(var(--i) * var(--knav-slat-step));         /* opening: left → right */
}

.knav-panel-inner, .knav-sizer { padding: var(--space-3) var(--space-3) var(--space-4); }
.knav-panel-inner { position: relative; z-index: 1; }   /* content over the slats */
.knav-slide { transition: transform .28s cubic-bezier(.4,0,.2,1), opacity .22s ease; }
.knav-sizer { position: absolute; left: -9999px; top: 0; width: var(--knav-panel-w); visibility: hidden; pointer-events: none; }

/* Panel content */
.knav-cols { display: flex; gap: var(--space-5); align-items: flex-start; }
.knav-col { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.knav-coltitle {
  font-family: var(--font-display);
  font-weight: var(--title-weight);
  font-stretch: var(--title-stretch);
  font-size: var(--text-sm);
  color: var(--mono-1);
  padding: var(--space-2) var(--space-3);
}
.knav-coltitle:not(:first-child) { margin-top: var(--space-3); }
.knav-cat {
  font-family: var(--font-display);
  font-weight: var(--subtitle-weight);
  font-stretch: var(--subtitle-stretch);
  font-size: var(--text-xs);
  color: var(--mono-3);
  padding: var(--space-2) var(--space-3);
}
.knav-cat:not(:first-child) { margin-top: var(--space-2); }
.knav-link {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  text-decoration: none;
}

/* ---- Accordion groups ----
   The subtitle IS the toggle: a .knav-coltitle button, the same markup in
   both panels (unified 2026-08-30 — Markkinointi's toggles were .knav-cat
   while a separate coltitle layer existed above them; that layer is gone).
   The links collapse through grid-template-rows 0fr → 1fr,
   which animates without measuring anything. The mega-panel's own height is
   an inline pixel value (the slat curtain needs it), so nav.js re-measures
   it off the sizer on every toggle — a delta would be wrong whenever the
   media column, not the links, is the tallest thing in the panel.
   Default state is authored in the panel FILES — first group OPEN, the rest
   closed, all in one column beside the media column — so the sizer measures
   the same state the panel opens with. Every open rebuilds from the
   template, so the first group is open on every open.
   The hamburger overlay neutralises all of this — see #menu in site.css. */
.knav-acc + .knav-acc { margin-top: var(--space-2); }
.knav-acc-toggle {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  background: none; border: 0; cursor: pointer; text-align: left;
  transition: color .15s ease;
}
.knav-acc-toggle:hover { color: var(--mono-1); }
.knav-acc-chev { flex: 0 0 auto; width: .85rem; height: .85rem; transition: transform .28s cubic-bezier(.4,0,.2,1); }
.knav-acc-chev svg { display: block; width: 100%; height: 100%; }
.knav-acc.is-open > .knav-acc-toggle .knav-acc-chev { transform: rotate(180deg); }
.knav-acc-panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .28s cubic-bezier(.4,0,.2,1); }
.knav-acc-list { overflow: hidden; min-height: 0; }
.knav-acc.is-open > .knav-acc-panel { grid-template-rows: 1fr; }
/* Media box */
.knav-col-media { align-self: stretch; }
.knav-media { position: relative; height: 100%; min-height: 14rem; border-radius: var(--radius-md); overflow: hidden; background: var(--mono-5); }
.knav-media video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.knav-media-logo { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 60%; max-width: 13rem; height: auto; pointer-events: none; }

/* Scanline panel art. The site-graphics artwork is SQUARE (1824×1824) and the
   media column is whatever height the panel happens to be, so a canvas
   stretched to the box would visibly distort it. Cover, then, but NOT by
   styling the canvas: the engine writes its own inline
   position:absolute;inset:0;width:100%;height:100% onto it, and inline wins.
   (3.23.1 — a rule here that centred with top/left:50% + translate lost the
   offsets to that inline inset:0 and kept only the translate, which parked
   the canvas one quarter into the box's top-left corner.)

   So nav.js mounts the engine on a stage of our own instead, and the canvas
   fills THAT. The stage is the square that covers the box: side = the larger
   of the two container dimensions, centred, cropped by overflow:hidden. The
   100% pair is the fallback for a browser without container query units —
   it stretches rather than collapsing to nothing. */
/* NO RADIUS and NO BACKGROUND on a box holding a canvas — the two together
   leave nothing for a seam to reveal.

   The radius was the last candidate for the corner thread once measurement
   had ruled out everything behind the canvas: a WebGL canvas is its own
   composited layer, and a rounded ancestor clip does not reliably resolve to
   full opacity along the curve. A square clip has no arc to seam.

   `background: none` has to be stated, not merely omitted — .knav-media above
   sets --mono-5, and that light grey is the one that read as a panel of its
   own against the #fff megamenu. Transparent, the box IS the panel, so a
   sub-pixel gap anywhere on the perimeter exposes white on white.
   overflow:hidden stays; the crop is what makes the cover stage work. */
.knav-media[data-scanline] { background: none; container-type: size; border-radius: 0; }
.knav-media-stage {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  /* --stage-cover is set on the BOX (see site-graphics-01 below) and read
     here through the var() fallback rather than declared on this element —
     a declaration here would win over the inherited value and the override
     would silently do nothing. 1 = the minimal covering square. */
  width: 100%; height: 100%;
  width: calc(max(100cqw, 100cqh) * var(--stage-cover, 1));
  height: calc(max(100cqw, 100cqh) * var(--stage-cover, 1));
  pointer-events: none;
}

/* THE OTHER HAIRLINE — the artwork's own frame edge, and this part is
   MEASURED rather than reasoned. Counting env.png pixels over R=128 (the
   level the shader's smoothstep crosses) in each outer line:

     -01  top 0 0 0 121 …   left 0 0 0 142 …   BOTTOM 128 139 …   RIGHT 49 51 …
     -02  top 6 8 11 …      left 0 0 0 4 …     bottom 9 12 …      right 1 2 …

   So -01 carries real ink in its very last row and column — a genuine dark
   edge on the bottom and right — while a 3px dead border protects its top and
   left. Every texture is CLAMP_TO_EDGE and the shader samples uEnv/uArt at
   xw/yPrime, coordinates the motion pushes PAST the frame, so that edge ink
   also gets smeared outward.

   6% clears it: the artwork edge lands outside the box on both axes, and it
   covers the ~2.6% of frame height the motion reaches (line.amp 41 +
   travel.amp 6 over an 1824 viewBox). -01 is a full-bleed backdrop whose crop
   is already deliberate, so this costs it nothing. NOT for -02 — a circle
   inside its frame, where overhang is a crop, and whose edge lines are 6-9
   pixels out of 1899 anyway.

   Re-baking -01 at the current lab rev is the repair at source; it needs the
   lab, this does not, and the two do not conflict. */
.knav-media[data-scanline="site-graphics-01"] { --stage-cover: 1.06; }

/* The Strateginen tuki artwork (-02 circle → -04 square → -05 square v2,
   all 2026-08-31) follows -01's lead: NO special sizing. The
   box is align-self:stretch + height:100% like every media box, so it
   expands and contracts with the accordions and the cover stage crops the
   circle top/bottom when the box is wider than tall. That crop was removed
   at 3.24.1 (height:auto + aspect-ratio:1/1 made the box square and the
   panel rigid); rigidity is now the worse trade — the panel's height belongs
   to the accordion column, and the artwork yields to it. If the square ever
   needs to come back, 3.24.1's caveat stands: derive it from the box's OWN
   width, never from calc()-on---knav-w. */
/* The artwork's entrance — the panel's slat curtain in miniature. nav.js
   mounts the box COVERED by these eight strips and adds .is-revealed once
   the menu has landed and the engine has a frame to show; the strips then
   drop away DOWNWARD, left → right — falling with the panel curtain's own
   sweep direction, on the same duration and stagger tokens. Built by nav.js
   only on the
   engine path, so reduced motion and no-WebGL never see them. z-index 1
   seats them above the stage (an earlier sibling with no stacking context
   of its own). The strips are rebuilt per open, so only the revealing
   direction of the transition ever runs. */
.knav-media-slats { position: absolute; inset: 0; display: flex; z-index: 1; pointer-events: none; }
.knav-media-slats i {
  flex: 1 1 0;
  background: #fff;
  transform: translateY(0);
  transition: transform var(--knav-slat-dur) var(--ease-standard, cubic-bezier(.4,0,.2,1));
  transition-delay: calc(var(--i) * var(--knav-slat-step));
}
/* overlap by a pixel — flex sub-pixel rounding opens seams otherwise */
.knav-media-slats i + i { margin-left: -1px; }
.knav-media[data-scanline].is-revealed .knav-media-slats i { transform: translateY(101%); }

/* Pre-ready, the stage is a canvas the engine has cleared to its default
   grey, sat over the fallback box — that pairing WAS the "residue": a grey
   slab with a dark thread at the corners for as long as the load took.
   The stage stays out of sight until .is-gl says there is a frame to show.
   With the idle prewarm in nav.js this window is normally never seen. */
.knav-media[data-scanline]:not(.is-gl) .knav-media-stage { visibility: hidden; }

/* Prewarm holder — nav.js parks the engines here between opens. Offscreen
   and hidden like .knav-sizer, and it MUST be offscreen rather than
   display:none: the engine sizes its bake from clientWidth, which
   display:none zeroes, and its IntersectionObserver is what pauses a parked
   engine — offscreen reads as "not intersecting", display:none reads as
   nothing at all. The boxes inside carry inline width/height copied from the
   rect the real box measured at, so the bake lands at the size it will show. */
.knav-prewarm { position: absolute; left: -9999px; top: 0; visibility: hidden; pointer-events: none; }

/* HISTORY, because the shape of the box still carries it. The background here
   used to be --mono-1 (#050505), and it was not decoration — it was the STATIC
   FALLBACK's backdrop, so that logo-mark-white.png would read before the
   engine was ready. That near-black is what the clip leaked as a dark thread
   at the corners, and down the SIDES on "Strateginen tuki", where the square
   box makes the stage exactly 100cqw and no cover overhang absorbs a
   fractional column width (~434.67px). It was whitened on .is-gl, then the
   radius went, and now the background goes with it.

   THE COST, stated: with the box transparent there is no dark backdrop left,
   and logo-mark-white.png is white — so the fallback mark is hidden rather
   than shipped invisible. Reduced motion, no WebGL and no JS therefore get an
   EMPTY media column, which for a decorative column is the quiet outcome. A
   visible fallback would need a dark mark asset; there is only a white one.

   The canvas's paper is #ffffff to match the panel (render.paper in each
   scanlines/*/scanline.json — it was #fafafa, the same light grey). */
.knav-media[data-scanline] .knav-media-logo { display: none; }

.knav-ico { flex: none; display: flex; color: var(--mono-1); transition: color .15s ease; }
.knav-link:hover .knav-ico { color: var(--green-3); }
.knav-ico svg { display: block; width: 20px; height: 20px; flex-shrink: 0; }
.knav-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.knav-txt b {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--mono-1);
  transition: color .15s ease;
}
.knav-link:hover .knav-txt b { color: var(--green-3); }
/* Hover arrow */
.knav-txt b::after {
  content: ""; display: inline-block;
  width: 0.85em; height: 0.85em; margin-left: 0.25em; vertical-align: -0.08em;
  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='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E") center / contain no-repeat;
  opacity: 0; transform: translateX(-3px);
  transition: opacity .18s ease, transform .18s ease;
}
.knav-link:hover .knav-txt b::after { opacity: 1; transform: translateX(0); }

/* ---- Scrim ---- */
.knav-scrim {
  position: fixed; inset: 0; z-index: 80;
  background: color-mix(in srgb, var(--mono-6) 30%, transparent);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  opacity: 0; pointer-events: none;
  transition: opacity .25s ease;
}
.knav-scrim.is-on { opacity: 1; }

/* ---- Responsive ---- */
@media screen and (max-width: 1400px) {
  .knav-items, .knav-cta, .knav-panel, .knav-scrim { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .knav, .knav-panel, .knav-scrim { transition: opacity .2s ease; }
  /* the curtain arrives, it does not sweep — and no stagger, or the panel
     would still take .4s to assemble itself one strip at a time */
  .knav-slats i { transition: none; transition-delay: 0s; }
  .knav-acc-panel, .knav-acc-chev { transition: none; }
  .knav-slide, .knav-btn, .knav-btn .btn-arrow, .knav-chev svg, .knav-item, .knav-ico, .knav-txt b, .knav-txt b::after { transition: none; }
}
