/* ===========================================================================
   TeskaLabs / LogMan.io — site stylesheet

   Type, spacing and components were read off the reference build at
   teskalabs.mysuitu.com (computed styles, Sept 2026), so the proportions are
   the brand's rather than a generic set:

     body copy   16px/24px        h1 72/68 w600 white centred
     h2 42/48 w600 accent, -.02em h3 20/26 w600 accent (card)
     buttons: pill r20, 15px w600, 2px border, 44px tall, padding 0 28px
     cards: r10, 30-40px padding
     container 1140px, section padding 60px, dark bands 100px, footer 120px

   THE GROUND IS LIGHT (Sept 2026). The page is near-white paper, the way the
   TeskaLabs apparel is: deep navy display type, one bright accent kept for
   MARKS AND GRAPHICS, and the low-poly ridge rising out of the bottom of the
   hero. A card is white ON the paper — lighter than the ground, not darker.

     --page --surface --surface-2 --sunken --hero-ground --band-ground
     --line --line-strong --line-soft --ink --ink-strong

   Palette tokens live in static/css/theme-<site>.css, loaded before this file,
   so both sites share every rule below and differ by a handful of values:
     teskalabs  accent #1e85f2, accent-ink #0d5db5, page #f4f7fb
     logman     identical to teskalabs (Sept 2026) — the sites are not
                distinguished by colour; only --art and the logo differ
   --accent is 2-3.5:1 on the paper, so it may NOT carry text: everything that
   is READ — headings, eyebrows, labels, links, button fills — takes
   --accent-ink. Never write a literal colour here; a new site must be a theme
   file, not a fork of this one.
   =========================================================================== */

:root {
  --white:       #ffffff;   /* text ON an accent fill, not a surface colour */
  --danger:      #b42318;   /* the form's send error; same red as the EPS calculator's */

  /* NOTE: --page / --surface / --surface-2 / --sunken / --hero-ground /
     --band-ground / --line / --line-strong / --line-soft / --ink /
     --ink-strong are set by the theme file and deliberately NOT repeated here.
     This stylesheet loads AFTER the theme, so a "fallback" copy at this
     specificity would silently win and both sites would wear one palette. */

  /* Verdana is the company font. It is a system font (macOS, Windows, iOS),
     not self-hosted; Geneva/Tahoma are the closest built-in stand-ins and
     other platforms fall through to their default sans-serif. Verdana ships
     only regular (400) and bold (700): 500 renders as regular, 600 as bold. */
  --font: Verdana, Geneva, Tahoma, sans-serif;
  /* Headings are Montserrat Medium — the face of the TeskaLabs logo and of the
     previous teskalabs.com — in place of the bold Verdana they had. It is the
     one web font on the site and it is self-hosted (static/fonts/, see the
     @font-face below), so there is still no request to anyone else's server.
     Only the 500 weight ships: to change --heading-weight, add that weight's
     file and an @font-face for it first, or the browser will fake it. */
  --font-display: "Montserrat", Verdana, Geneva, Tahoma, sans-serif;
  --heading-weight: 500;
  --container: 1140px;

  /* Responsive scale ------------------------------------------------------
     Breakpoints (see the RESPONSIVE section at the foot of this file):
       1180px  the nav becomes a drawer
       1080px  desktop grids collapse to tablet-landscape
        880px  zigzag rows stack
        700px  tablet portrait
        560px  phone
        380px  small phone
     Gutters and vertical rhythm are fluid between those, so the breakpoints
     only ever change layout, never size: 16px gutters on a 360px phone up to
     20px past 900px, and the 60/100px section rhythm compresses to 36/56px. */
  --pad-x: clamp(16px, 0.85rem + 1.4vw, 32px);
  --section-y: clamp(44px, 1.7rem + 4.4vw, 76px);
  --band-y: clamp(56px, 2rem + 6vw, 100px);
  --header-h: 72px;          /* kept in sync with .site-header__inner */
  --radius-card: 10px;
  --radius-pill: 20px;
  --radius-panel: 20px;

  /* How big the ridge art is drawn. At full width one file's width matches the
     viewport's, which on a phone leaves a 100px-tall ridge — too small to read
     as a mountain — so narrow screens zoom in and crop the sides instead. */
  --art-size: 100% auto;

  /* Depth. On paper a card is lifted by a soft shadow tinted with the page's
     own ink — never neutral grey, which goes muddy — plus the hairline that
     cuts it out. --edge is what the dark ground needed (a lit top edge) and
     is deliberately a no-op here; the rules that use it are unchanged. */
  --shade: 8, 33, 63;
  --edge: 0 0 transparent;
  --lift: 0 1px 2px rgba(var(--shade), .05), 0 10px 24px -14px rgba(var(--shade), .17);
  --lift-hover: 0 2px 4px rgba(var(--shade), .07), 0 22px 44px -18px rgba(var(--shade), .25);
  --glow-accent: 0 0 0 1px color-mix(in srgb, var(--accent) 34%, transparent),
                 0 18px 44px -24px color-mix(in srgb, var(--accent) 60%, transparent);

  /* Four-step tint ladder, mixed from whatever accent the theme file set, so
     each site gets its own. Used by the edition cards and the matrix column
     heads to show progression before a word is read. The mix is toward
     --page, so on this light ground the ladder runs PALE -> SATURATED. */
  --tint-1: color-mix(in srgb, var(--accent) 30%, var(--page));
  --tint-2: color-mix(in srgb, var(--accent) 52%, var(--page));
  --tint-3: color-mix(in srgb, var(--accent) 76%, var(--page));
  --tint-4: var(--accent);

  color-scheme: light;       /* native controls, scrollbars and form widgets */
}

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

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  background: var(--page);
  /* Belt and braces: nothing should overflow sideways, but if a long unbroken
     string ever does, the page must not become horizontally pannable.
     `clip` rather than `hidden` so it does not create a scroll container
     (which would break position: sticky inside the page). */
  overflow-x: clip;
}
/* Anchor targets sit below the fixed header rather than under it. */
:target { scroll-margin-top: calc(var(--header-h) + 16px); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;

  /* The node field: a 32px matrix of faint accent dots over the whole page —
     the flat version of the network in the apparel art. Barely visible on its
     own, but it is the difference between a blank fill and a surface, and it
     is what the white cards then sit ON. */
  background-color: var(--page);
  background-image: radial-gradient(color-mix(in srgb, var(--accent) 24%, transparent) 1px, transparent 1px);
  background-size: 32px 32px;
  background-position: -1px -1px;
}

/* Montserrat Medium, subset to Latin + Latin Extended-A (English, Czech,
   Polish) — 19 kB. SIL Open Font License 1.1; see static/fonts/README.md for
   the source and the command that made it. `swap` shows the heading in Verdana
   until the file arrives; base.html preloads it so that is rarely seen. */
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/montserrat-500.woff?v=20261030110722") format("woff");
}

h1, h2, h3, h4 { margin: 0 0 20px; font-family: var(--font-display); font-weight: var(--heading-weight); }

h1 {
  font-size: clamp(2.1rem, 1.1rem + 3.4vw, 4.5rem);   /* 72px at full width */
  /* 0.95 is right for a 72px display line; at phone sizes it collides, so the
     leading opens up as the type shrinks (tightened again at >=880px below). */
  line-height: 1.1;
  color: var(--ink-strong);
  text-wrap: balance;
}
h2 {
  font-size: clamp(1.85rem, 1.2rem + 1.9vw, 2.625rem); /* 42px */
  line-height: 1.14;
  letter-spacing: -0.02em;
  color: var(--accent-ink);
}
/* A newline in a heading's content starts a new line (issue #26); the `lines`
   filter in build.py wraps each line. text-wrap: balance inherits, so a line
   that still wraps on a phone wraps evenly. */
h1 .line, h2 .line { display: block; text-wrap: balance; }
h3 { font-size: 1.25rem; line-height: 1.3; color: var(--accent-ink); }  /* 20px */

p { margin: 0 0 1rem; }
p:last-child { margin-bottom: 0; }

/* Inline links take the darkened accent: the signature hue is 2-3.5:1 on the
   paper, well short of the 4.5:1 body-copy contrast; --accent-ink clears it
   on both sites. */
a { color: var(--accent-ink); text-underline-offset: 2px; }
a:hover { text-decoration: none; }

::selection { background: color-mix(in srgb, var(--accent) 28%, transparent); color: var(--ink-strong); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

.wrap {
  width: 100%; max-width: var(--container); margin-inline: auto;
  /* env() keeps content clear of a notch / rounded corner in landscape. */
  padding-left: max(var(--pad-x), env(safe-area-inset-left));
  padding-right: max(var(--pad-x), env(safe-area-inset-right));
}
.wrap--narrow { max-width: 860px; }

.skip-link { position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--accent-ink); color: var(--white); padding: .6rem 1rem; }
.skip-link:focus { left: .5rem; top: .5rem; }

.notice { margin: 0; padding: calc(var(--header-h) + 12px) var(--pad-x) 12px; text-align: center;
  background: var(--sunken); color: var(--ink); font-size: .85rem;
  border-bottom: 1px solid var(--line); }

/* ---------------------------------------------------------------- header -- */

.site-header {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  /* Translucent rather than solid: the page moves underneath it, which is the
     single cheapest way to make a fixed bar feel like software. The solid
     colour is the fallback where backdrop-filter is unsupported. */
  background: color-mix(in srgb, var(--band-ground) 86%, transparent);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 22px -12px rgba(var(--shade), .35);
  transition: background .25s ease, border-color .25s ease, box-shadow .25s ease;
}
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .site-header { -webkit-backdrop-filter: blur(14px) saturate(140%); backdrop-filter: blur(14px) saturate(140%); }
}
/* JS adds .at-top while the page is scrolled to the top of a hero, so the bar
   dissolves into it. Without JS the header simply stays solid. */
.site-header.at-top { background: transparent; border-bottom-color: transparent; box-shadow: none; }

.site-header__inner { display: flex; align-items: center; gap: 20px; min-height: var(--header-h); flex-wrap: nowrap; }

.logo { display: inline-flex; align-items: center; gap: 10px; min-width: 0;
  min-height: 44px;                     /* a finger-sized target for "home" */
  text-decoration: none; color: var(--ink-strong); }
.logo__mark { width: 26px; height: 26px; flex: none; }
/* `object-fit: contain` is the safety net: if the bar ever runs out of room the
   logo scales down in proportion instead of being squashed sideways (height is
   fixed, so a narrower box used to distort it). */
.logo__full { display: block; height: 44px; width: auto; max-width: 100%;
  object-fit: contain; object-position: left center; }
.logo__text { font-size: 1.35rem; font-weight: 600; letter-spacing: -.01em; line-height: 1; }
.logo__text strong { font-weight: 700; }
.logo__sub { display: block; font-size: .5rem; font-weight: 600; letter-spacing: .18em;
  text-transform: uppercase; color: var(--accent-ink); margin-top: 3px; }

.site-nav { display: flex; align-items: center; gap: 16px; margin-left: auto; flex-wrap: nowrap; }
/* Gap and side padding are sized so the widest bar — the logo image plus the
   Czech or Polish labels — fits the 1140px container with the logo at full
   size. At 6px / 14px it was ~20px short and the logo was squeezed even on a
   wide screen. */
.site-nav__list { display: flex; align-items: center; gap: 4px; list-style: none; margin: 0; padding: 0; }
.site-nav__list a {
  position: relative;
  display: block; padding: 12px 11px; border-radius: var(--radius-pill);
  color: var(--ink-strong); text-decoration: none;
  font-size: .875rem; font-weight: 500; letter-spacing: .02em; text-transform: uppercase;
  white-space: nowrap; transition: background .15s, color .15s;
}
.site-nav__list a:hover { background: color-mix(in srgb, var(--accent) 11%, transparent);
  color: var(--accent-ink); }
.site-nav__list a[aria-current="page"] { color: var(--accent-ink); }
/* The current page carries a lit marker rather than only a colour change. */
.site-nav__list a[aria-current="page"]::after {
  content: ""; position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 2px; width: 16px; height: 2px; border-radius: 2px; background: var(--accent);
}
.site-nav__end { display: flex; align-items: center; gap: 12px; }

/* The drawer's control. The checkbox is the real control — it must stay in the
   tab order and be reachable by assistive tech, so it is clipped rather than
   `display: none`; the label next to it draws the burger. Both are hidden
   outright above the nav breakpoint, where the full nav bar is shown. */
.navtoggle { position: absolute; width: 1px; height: 1px; opacity: 0;
  margin: 0; pointer-events: none; }
.navtoggle__btn, .navdrawer__scrim { display: none; }
/* The panel's own CTA. Scoped past `.btn`, which also sets display. */
.site-nav .site-nav__cta { display: none; }

.langswitch { position: relative; }
.langswitch > summary {
  list-style: none; cursor: pointer; color: var(--ink-strong);
  /* 40px tall so it is a finger target, not just a mouse target. */
  display: inline-flex; align-items: center; min-height: 40px;
  font-size: .8rem; font-weight: 600; letter-spacing: .08em;
  border: 1px solid var(--line-strong); border-radius: var(--radius-pill); padding: 7px 14px;
  transition: border-color .15s, color .15s;
}
.langswitch > summary::-webkit-details-marker { display: none; }
.langswitch > summary:hover { border-color: var(--accent); color: var(--accent-ink); }
.langswitch ul {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 120;
  margin: 0; padding: 6px; list-style: none; min-width: 150px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: var(--lift);
}
.langswitch li a { display: flex; align-items: center; min-height: 44px;
  padding: 8px 14px; border-radius: 8px;
  color: var(--ink-strong); text-decoration: none; font-size: .9rem; }
.langswitch li a:hover { background: var(--sunken); color: var(--accent-ink); }
.langswitch li a[aria-current="true"] { color: var(--accent-ink); font-weight: 600; }

/* --------------------------------------------------------------- buttons -- */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0 28px;
  border: 2px solid transparent; border-radius: var(--radius-pill);
  font: inherit; font-size: .9375rem; font-weight: 600; text-decoration: none;
  white-space: nowrap; text-align: center;
  cursor: pointer; transition: background .18s, border-color .18s, color .18s, box-shadow .18s, transform .18s;
}
/* The fill is --accent-ink, not --accent: white on the signature blue is
   3.0:1 and on the LogMan teal 2.3:1, so the bright hue can hold a mark but
   never a label. */
.btn--primary { background: var(--accent-ink); color: var(--white);
  box-shadow: 0 10px 24px -14px color-mix(in srgb, var(--accent-ink) 60%, transparent); }
.btn--primary:hover { background: var(--accent-dark);
  box-shadow: 0 14px 28px -12px color-mix(in srgb, var(--accent-ink) 70%, transparent); }
.btn--secondary { background: var(--surface); color: var(--accent-ink);
  border-color: color-mix(in srgb, var(--accent-ink) 40%, transparent); }
.btn--secondary:hover { background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  border-color: var(--accent-ink); color: var(--accent-dark); }
.btn--sm { min-height: 40px; padding: 0 22px; font-size: .875rem; }
.btn:active { transform: translateY(1px); }

/* The hero and the CTA panel are paper too, so the ghost button needs no
   inversion there — only a firmer edge against a tinted ground. */
.hero .btn--secondary, .band--cta .btn--secondary {
  border-color: color-mix(in srgb, var(--accent-ink) 55%, transparent); }

.actions { display: flex; flex-wrap: wrap; gap: 16px; margin: 40px 0 0; }
.hero .actions, .band--cta .actions, .band--center .actions { justify-content: center; }

/* --------------------------------------------------------------- sections -- */

/* The eyebrow carries a short lit rule. It is the smallest possible piece of
   instrumentation and it appears above every section head on the site, so it
   is what stops a page reading as a stack of unrelated blocks. */
.eyebrow { display: flex; align-items: center; gap: 10px;
  margin: 0 0 14px; font-size: .8125rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--accent-ink); }
.eyebrow::before { content: ""; width: 26px; height: 2px; flex: none; border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--accent)); }
.band__head .eyebrow, .hero .eyebrow { justify-content: center; }
.band__head .eyebrow::after, .hero .eyebrow::after {
  content: ""; width: 26px; height: 2px; flex: none; border-radius: 2px;
  background: linear-gradient(270deg, transparent, var(--accent)); }

.lead { font-size: 1.0625rem; line-height: 1.6; max-width: 70ch; }

.band { position: relative; padding: var(--section-y) 0; }
.band--alt { background: color-mix(in srgb, var(--accent) 5%, var(--page)); }
/* Every section head is centred — eyebrow, heading and lead — so the page has
   one axis from the hero down to the form. (It used to depend on `align:
   center` in the YAML, which left some heads on the left and some in the
   middle on the same page.) What sits under a head keeps its own alignment:
   card and timeline headings stay left with their text. */
.band__head { max-width: 900px; margin-inline: auto; text-align: center; }
.band__head .lead { margin-inline: auto; }

/* hero ------------------------------------------------------------------- */

.hero {
  position: relative; isolation: isolate;
  background: var(--hero-ground);
  /* Clears the fixed header at every width without a per-breakpoint override. */
  padding: calc(var(--header-h) + clamp(48px, 7.5vw, 116px)) 0 clamp(84px, 10vw, 132px);
  text-align: center;
  color: var(--ink);
  overflow: hidden;
  border-bottom: 1px solid var(--line);
}
/* Backdrop. Three layers, front to back: the apparel art (--art: the low-poly
   ridge, the node net and the rook, anchored to the bottom edge and scaled to
   the full width), a blue haze rising off that bottom edge, and the paper
   gradient under all of it. (There used to be a 64px measuring grid between
   the haze and the gradient; it was removed in Oct 2026 — the backdrops are
   plain blue washes now, here, in the CTA panel and in the form band.) The art
   is one cached file
   per site rather than inline markup on every page. Swap in a photograph by
   setting a background-image here — the layers still read over one. */
.hero__bg {
  position: absolute; inset: 0; z-index: -1;
  background:
    /* An optional free-standing figure in front of the art (LogMan.io's
       lighthouse rook, theme-logman.css): none unless site.js (section 8)
       has sized it to fit beside the hero copy. */
    var(--rook-on, none) left var(--rook-x, 0px) bottom var(--rook-y, 0px) / auto var(--rook-h, 0px) no-repeat,
    var(--art) bottom center / var(--art-size) no-repeat,
    radial-gradient(120% 80% at 50% 116%, var(--glow) 0%, var(--glow-mid) 45%, transparent 72%),
    linear-gradient(180deg, var(--grad-top) 0%, var(--grad-mid) 48%, var(--grad-bottom) 100%);
}
/* Two fades in one layer. The top of the net dissolves towards
   the TOP, so the art reads as ground the content stands on rather than
   wallpaper behind it; and the foot of the ridge is washed back out into the
   page colour at the BOTTOM, so the hero ends by dissolving into the paper
   instead of on a hard blue cut. */
.hero__bg::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(180deg, var(--grad-top) 0%, transparent 46%),
    linear-gradient(0deg, var(--page) 0%, color-mix(in srgb, var(--page) 55%, transparent) 32px, transparent 112px);
}
/* The interactive backdrop (`fx: <preset>`, static/js/hero-fx.js): a
   WebGPU canvas multiplied onto the hero — it draws only white and tints of
   --accent, and white changes nothing. It sits ABOVE the art and the hero's
   fades (z-index), so it keeps its colour all the way up; its own mask fades it out
   under the header and along the bottom edge instead. It fades in on its first
   frame; until then, and wherever it never runs, the hero is as above. */
.hero__fx {
  position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%;
  mix-blend-mode: multiply; pointer-events: none;
  opacity: 0; transition: opacity .8s ease;
  /* Two masks, intersected:
     - a soft ellipse over the whole copy column, headline to the note under
       the buttons, holding the effect to --fx-core of its strength there, so
       the hero text keeps its contrast (README);
     - top and bottom fades: nothing under the header bar, nothing at the
       hairline that closes the hero. */
  --fx-core: .1;
  --fx-copy: radial-gradient(ellipse min(50vw, 40rem) 24rem at 50% 53%,
             rgb(0 0 0 / var(--fx-core)) 70%, #000 100%);
  --fx-edges: linear-gradient(180deg, transparent var(--header-h), #000 calc(var(--header-h) + 72px),
              #000 calc(100% - 80px), transparent 100%);
  -webkit-mask-image: var(--fx-copy), var(--fx-edges);
  -webkit-mask-composite: source-in;
          mask-image: var(--fx-copy), var(--fx-edges);
          mask-composite: intersect;
}
.hero__fx.is-on { opacity: 1; }
/* A hero with an interactive backdrop wears the flow art instead of the
   apparel composition: streamlines parting round the copy column and riding
   the ridge, a still of what the shader does — and what visitors without it
   see. It is drawn at a fixed scale, sized by HEIGHT, so the gap it leaves
   for the copy lines up with the copy at every width (scripts/gen_art.py).
   Not below 700px: there the copy fills the width, the streamlines have
   nowhere to pass it, so phones keep the apparel art, zoomed for them as
   above (the shader, when the phone has WebGPU, runs over it). */
@media (min-width: 701px) {
  .hero[data-fx] .hero__bg { --art: var(--art-flow); --art-size: auto 800px; }
}
/* The liquid is a pale, passing wash: it can show twice as much there. */
.hero[data-fx="liquid"] .hero__fx { --fx-core: .2; }
/* Fog fills the whole hero (so do Nik's editor compositions), so
   the calm area round the copy would show as an oval with an edge; a longer,
   softer ramp lets the mist thin out towards the text instead. */
.hero[data-fx="fog"] .hero__fx,
.hero[data-fx="lunarterrace"] .hero__fx,
.hero[data-fx="floralgalaxy"] .hero__fx,
.hero[data-fx="starlitkite"] .hero__fx {
  --fx-copy: radial-gradient(ellipse min(52vw, 42rem) 28rem at 50% 58%,
             rgb(0 0 0 / var(--fx-core)) 42%, #000 100%);
}
/* A hairline along the bottom edge — the section's own signature. */
.hero::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 55%, transparent) 30%,
              color-mix(in srgb, var(--accent) 55%, transparent) 70%, transparent);
}
.hero .wrap { position: relative; }
.hero h1 { margin-bottom: 28px; max-width: 26ch; margin-inline: auto; text-wrap: balance; }
/* 26ch lets a 40-character heading ("C-ITS PKI, na které jezdí evropské
   silnice.") sit on two balanced lines instead of a narrow three-line stack. A long heading stacked six lines deep in it,
   so the macro marks those `hero--long`: the full container width and one step
   down in size. */
.hero--long h1 { max-width: none; font-size: clamp(1.9rem, 1rem + 2.4vw, 3.25rem); line-height: 1.1; }
.hero .lead { font-size: clamp(1rem, .9rem + .5vw, 1.5rem); line-height: 1.3;
  color: var(--ink); max-width: 62ch; margin-inline: auto; }
.hero .eyebrow { color: var(--accent-ink); }
.hero__byline { margin: 24px 0 0; color: var(--accent-ink); font-size: 1rem; }
.hero__note { margin: 48px 0 0; color: var(--ink); font-size: 1rem; }

/* cards ------------------------------------------------------------------ */

.cards, .grid { display: grid; gap: 24px; margin-top: 40px; }
.cards { grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); }
.grid  { grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); }
/* `columns: 2` in the YAML: two fixed columns, so four cards sit 2 + 2 (the
   row-of-four rule is skipped for these); one column on narrow screens. */
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 640px) { .grid--2 { grid-template-columns: 1fr; } }

/* The surface every panel on the site is made of — the same on both sites:
   one step above the ground, a hairline to cut it out, tight 4px corners and
   a shadow underneath. */
.card, .stack__item, .steps__item, .stat, .faq__item, .tablewrap {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 4px;
  box-shadow: var(--edge), var(--lift);
}
/* Text boxes (a heading and a paragraph — product cards, grid and feature
   items), step boxes and stat tiles rest with a thin blue rule along the top
   edge and no wash. On hover the rule becomes a 3px edge shading light blue →
   dark blue, a faint blue wash fades in under it and the box lifts. FAQ items
   and the table box keep the plain panel; the editions ladder (`steps--ladder`)
   is deliberately boxless and is left out. The resting rule is a background
   layer, not a pseudo-element, because `.steps__item::before` is the number
   chip; the hover state is `::after`, faded in, under the content. */
.card, .stack__item, .steps:not(.steps--ladder) > .steps__item, .stat {
  position: relative; overflow: hidden;
  background:
    linear-gradient(90deg, var(--accent), transparent 70%) top / 100% 2px no-repeat,
    var(--surface);
}
:is(.card, .stack__item, .steps:not(.steps--ladder) > .steps__item, .stat) > *,
.steps:not(.steps--ladder) > .steps__item::before { position: relative; z-index: 1; }
:is(.card, .stack__item, .steps:not(.steps--ladder) > .steps__item, .stat)::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 120px; z-index: 0;
  pointer-events: none; opacity: 0; transition: opacity .25s ease;
  background:
    linear-gradient(90deg, color-mix(in srgb, var(--accent) 30%, var(--page)), var(--accent) 55%, var(--accent-ink)) top / 100% 3px no-repeat,
    linear-gradient(180deg, color-mix(in srgb, var(--accent) 14%, transparent), transparent);
}
@media (hover: hover) {
  :is(.card, .stack__item, .steps:not(.steps--ladder) > .steps__item, .stat):hover {
    background: var(--surface-2);
    box-shadow: var(--edge), var(--lift-hover); transform: translateY(-2px); }
  :is(.card, .stack__item, .steps:not(.steps--ladder) > .steps__item, .stat):hover::after { opacity: 1; }
}

.card {
  padding: 26px 22px 28px;
  /* A column so a card's last line (the "who it is for" note) can be pinned to
     the bottom and line up across a row of unequal cards. */
  display: flex; flex-direction: column;
}
.card, .stack__item, .steps__item, .stat { transition: box-shadow .2s, transform .2s, background .2s; }
.card h3 { margin-bottom: 14px; }
.card__eyebrow { margin: 0 0 10px; font-size: .8125rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink); }
/* `margin-top: auto` pins the note to the bottom of the card, so the "who it
   is for" lines align across a row of cards with unequal copy. Do not give it
   a margin-top elsewhere — that is what the auto is for. */
.card__note { margin: auto 0 0; padding-top: 16px; border-top: 1px solid var(--line);
  font-size: .875rem; line-height: 1.5; color: var(--ink); }
.card p + .card__note { padding-top: 18px; }
/* `cards--tinted` (the editions ladder) used to carry the tinted look alone;
   it is now the look of every panel, so the class is kept only for markup
   compatibility. */
.card__action { margin: 24px 0 0; }
.card__action a {
  display: inline-flex; align-items: center; gap: 10px;
  color: var(--ink-strong); font-size: .95rem; font-weight: 600; text-decoration: none;
}
.card__action a:hover { color: var(--accent-ink); }
/* the circular arrow chip used all over the reference design */
.card__action a::after {
  content: ""; width: 22px; height: 22px; flex: none; border-radius: 50%;
  background: var(--accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 6l6 6-6 6'/%3E%3C/svg%3E") center/12px no-repeat;
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 60%, transparent);
  transition: transform .18s, box-shadow .18s;
}
.card__action a:hover::after { transform: translateX(3px);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 18%, transparent); }
.card__address { color: var(--ink); font-size: .95rem; }

/* two-column intro + card grid ("Why teams switch to us") */
.split { display: grid; gap: 32px; grid-template-columns: minmax(240px, 1fr) 2.1fr; align-items: start; margin-top: 40px; }
.split__intro h3 { font-size: 1.5rem; line-height: 1.2; }
.split__intro p { font-size: .95rem; }
.split__items { display: grid; gap: 24px; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }

/* plain feature stack */
.stack { display: grid; gap: 24px; margin-top: 40px;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); }
.stack__item { padding: 26px 22px 28px; }
/* A block that carries a button is a column, so the button sits on the bottom
   edge and the buttons line up across a row of unequal copy. */
.stack__item--action { display: flex; flex-direction: column; }
.stack__item--action .actions { margin: auto 0 0; padding-top: 24px; }

/* long-form legal text (privacy policy) ---------------------------------- */

/* Reading type, not display type: the section headings step down from the
   42px h2 the marketing bands use, and the measure is the narrow wrap. */
.legal h2 { font-size: 1.5rem; line-height: 1.25; letter-spacing: 0; margin: 48px 0 16px; }
.legal h2:first-child { margin-top: 0; }
.legal h3 { font-size: 1.0625rem; color: var(--ink-strong); margin: 32px 0 12px; }
.legal ul { margin: 0 0 1rem; padding-left: 1.25rem; }
.legal li { margin-bottom: .5rem; }
.legal li::marker { color: var(--accent); }
.legal a { overflow-wrap: anywhere; }

/* zigzag ----------------------------------------------------------------- */

.zigzag { display: grid; gap: 56px; margin-top: 48px; }
.zigzag__item { display: grid; gap: 32px; grid-template-columns: 1fr 1fr; align-items: center; }
.zigzag__item:nth-child(even) .zigzag__text { order: 2; }
/* The figure is a plain blue panel: a glow under the glyph and corner brackets
   framing it. It stands in for a product illustration — set a background-image
   here when there is one. */
.zigzag__figure {
  position: relative; isolation: isolate;
  border-radius: var(--radius-card); min-height: 240px; overflow: hidden;
  border: 1px solid var(--line);
  background:
    radial-gradient(70% 60% at 50% 60%, color-mix(in srgb, var(--accent) 13%, transparent), transparent 70%),
    linear-gradient(160deg, var(--surface), var(--sunken));
  box-shadow: var(--edge), var(--lift);
  display: grid; place-items: center;
}
/* Corner brackets: two gradients clipped to 22px squares in opposite corners. */
.zigzag__figure::before {
  content: ""; position: absolute; inset: 14px; pointer-events: none;
  background:
    linear-gradient(var(--accent), var(--accent)) left top / 22px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) left top / 1px 22px no-repeat,
    linear-gradient(var(--accent), var(--accent)) right bottom / 22px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) right bottom / 1px 22px no-repeat;
  opacity: .55;
}
.zigzag__glyph { position: relative; width: 92px; height: 92px; border-radius: 24px;
  background: linear-gradient(150deg, var(--accent-ink), var(--accent) 55%, var(--accent-dark));
  box-shadow: 0 16px 34px -14px color-mix(in srgb, var(--accent-ink) 55%, transparent),
              inset 0 1px 0 rgba(255,255,255,.45); }
/* Two rings pushing out from the glyph — a signal leaving a node. */
.zigzag__glyph::before, .zigzag__glyph::after {
  content: ""; position: absolute; inset: -22px; border-radius: 34px;
  border: 1px solid color-mix(in srgb, var(--accent) 42%, transparent);
}
.zigzag__glyph::after { inset: -44px; border-radius: 46px;
  border-color: color-mix(in srgb, var(--accent) 20%, transparent); }
/* An illustration in the panel (`figure:` on the item, scripts/gen_figures.py)
   sits centred at most of the panel's width; its own ground is transparent, so
   the panel's gradient and brackets stay. */
.zigzag__img { position: relative; display: block; width: 84%; max-width: 480px; height: auto; margin: 18px auto; }
.zigzag h3 { font-size: 1.75rem; line-height: 1.15; }

/* stats ------------------------------------------------------------------ */

.stats { display: grid; gap: 24px; margin: 40px 0 0;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); }
.stat { position: relative; overflow: hidden; margin: 0; padding: 34px 28px 30px; }
.stat__value { font-size: clamp(1.6rem, 1.05rem + 1.5vw, 2.25rem);
  font-family: var(--font-display); font-weight: var(--heading-weight);
  color: var(--ink-strong); line-height: 1;
  /* Figures line up column to column, and a touch of tracking keeps a 6-digit
     number from reading as a wall. */
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.stat__label { margin: 14px 0 0; color: var(--accent-ink); font-weight: 600; }
.stat__note { display: block; margin-top: 8px; font-weight: 400; color: var(--ink); font-size: .9rem; }

/* timeline --------------------------------------------------------------- */

.timeline { list-style: none; margin: 40px 0 0; padding: 0 0 0 28px;
  border-left: 1px solid var(--line); }
.timeline__item { position: relative; padding-bottom: 36px; }
.timeline__item:last-child { padding-bottom: 0; }
.timeline__item::before { content: ""; position: absolute; left: -34px; top: 6px;
  width: 11px; height: 11px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 16%, transparent),
              0 0 0 5px var(--page); }
.timeline__year { margin: 0 0 6px; font-size: .875rem; font-weight: 700;
  letter-spacing: .06em; color: var(--accent-ink); font-variant-numeric: tabular-nums; }
.timeline h3 { color: var(--ink-strong); font-size: 1.25rem; margin-bottom: 10px; }

/* faq -------------------------------------------------------------------- */

.faq { margin-top: 40px; }
.faq__item { margin-bottom: 12px; overflow: hidden;   /* the tinted question row keeps to the rounded corners */
  transition: border-color .2s, background .2s; }
.faq__item[open], .faq__item:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }
/* The question row sits on the palest blue (7 % of the accent over the
   panel's white); the answer under it stays white, so an open item reads as
   question on tint, answer on paper. */
.faq__item summary { display: flex; justify-content: space-between; gap: 20px; align-items: center;
  cursor: pointer; list-style: none; padding: 22px 26px;
  background: color-mix(in srgb, var(--accent) 7%, var(--surface));
  font-size: 1.05rem; font-weight: 600; color: var(--accent-ink); }
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary::after { content: ""; width: 22px; height: 22px; flex: none; border-radius: 50%;
  background: var(--accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center/12px no-repeat;
  transition: transform .2s; }
.faq__item[open] summary::after { transform: rotate(180deg); }
.faq__item p { padding: 0 26px 24px; margin: 0; }

/* steps (numbered pipeline) ---------------------------------------------- */

.steps { list-style: none; counter-reset: step; margin: 40px 0 0; padding: 0;
  display: grid; gap: 24px; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.steps__item { counter-increment: step; position: relative; padding: 30px; }
/* The index is a chip, not a caption: a bordered square with the number in it,
   so a row of steps reads as stages in a pipeline. */
.steps__item::before {
  content: counter(step, decimal-leading-zero);
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; margin-bottom: 16px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  font-size: .8125rem; font-weight: 700; letter-spacing: .06em; color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.steps__item h3 { color: var(--ink-strong); margin-bottom: 10px; }
.steps__item p { font-size: .95rem; margin-bottom: 0; }

/* `layout: ladder`: named steps rather than numbered ones, marked with a rule
   instead of a card — an upgrade path, not a procedure. */
.steps--ladder { grid-template-columns: repeat(auto-fit, minmax(min(270px, 100%), 1fr)); gap: 28px; }
.steps--ladder .steps__item { background: transparent; border: 0; box-shadow: none;
  border-radius: 0; padding: 2px 0 2px 18px;
  border-left: 2px solid var(--accent); }
.steps--ladder .steps__item::before { content: none; }
.steps__item .steps__label { margin: 0 0 8px; font-size: .8125rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--accent-ink); }
.steps--ladder .steps__item h3 { font-size: 1.0625rem; margin-bottom: 8px; }

/* comparison table ------------------------------------------------------- */

/* `position: relative` matters: the yes/no cells carry absolutely positioned
   .visually-hidden labels, and without a positioned ancestor they escape the
   scroller and make the whole PAGE scroll sideways on a phone. */
.tablewrap { position: relative; margin-top: 40px; overflow-x: auto; }
.ftable { width: 100%; border-collapse: collapse; min-width: 620px; text-align: left; }
.ftable th, .ftable td { padding: 14px 20px; border-bottom: 1px solid var(--line); }
.ftable thead th { font-size: .8rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--accent-ink); white-space: nowrap;
  vertical-align: bottom; padding-bottom: 12px; }
.ftable tbody th { font-weight: 500; color: var(--ink-strong); font-size: .95rem;
  vertical-align: top; }
.ftable thead th:first-child { width: 34%; min-width: 200px; }
/* Edition names sit centred over their columns of ticks and values. */
.ftable thead th:not(:first-child) { text-align: center; }
.ftable td { text-align: center; width: 1%; white-space: nowrap; vertical-align: top; }
.ftable tbody tr:last-child th, .ftable tbody tr:last-child td { border-bottom: 0; }

/* The second line under a capability: what it actually covers. Small, quiet,
   and the reason the matrix can be read without a glossary beside it. */
.ftable tbody th small { display: block; margin-top: 5px;
  font-size: .8125rem; font-weight: 400; line-height: 1.4; color: var(--ink); }

/* The column heads used to carry a tinted rule beneath the label
   (`.ftable__bar`, the editions ladder); removed 2026-10-17. */

/* Group bands break 24 rows into four readable blocks. A pale wash was not
   enough to read as a divider at a glance, so they are cut INTO the surface:
   one step below the paper, deep navy caps, and an accent rule on top. */
.ftable tbody th.ftable__group {
  padding: 12px 20px; font-size: .8125rem; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-strong);
  background: var(--sunken);
  border: 0; border-top: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
}
.ftable__grouprow + tr th, .ftable__grouprow + tr td { border-top: 0; }

/* A value rather than a tick: "18 months", "unlimited", "500 000+ EPS". */
.ftable__val { display: inline-block; padding: 3px 10px; border-radius: 6px;
  font-size: .8125rem; font-weight: 500; line-height: 1.5; white-space: nowrap;
  color: var(--ink-strong);
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent); }
/* A ceiling, not a feature — the thing that decides between two editions. */
.ftable__val--limit { color: var(--signal-ink, var(--signal)); background: var(--signal-wash);
  border-color: color-mix(in srgb, var(--signal) 40%, transparent); }

@media (hover: hover) {
  .ftable tbody tr:hover th, .ftable tbody tr:hover td {
    background: color-mix(in srgb, var(--accent) 8%, var(--surface)); }
  .ftable tbody tr:hover th.ftable__group { background: var(--sunken); }
}

/* Qualifications that belong to the matrix, not to a section of their own. */
.tablenotes {
  list-style: none; margin: 24px 0 0; padding: 18px 0 0;
  border-top: 1px solid var(--line);
  display: grid; gap: 12px 40px;
  grid-template-columns: repeat(auto-fit, minmax(min(330px, 100%), 1fr));
  font-size: .875rem; line-height: 1.6; text-align: left;
}
.tablenotes strong { color: var(--ink-strong); }
.ftable .yes::before { content: ""; display: inline-block; width: 20px; height: 20px;
  border-radius: 50%; background: var(--accent)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13l4 4L19 7'/%3E%3C/svg%3E") center/12px no-repeat; }
.ftable .no::before { content: ""; display: inline-block; width: 20px; height: 20px;
  border-radius: 50%; border: 1px solid var(--line-strong); }

/* screen-reader-only text (the table's yes/no cells) */
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* lists / offices -------------------------------------------------------- */

.linklist { list-style: none; margin: 16px 0 0; padding: 0; }
.linklist li { margin-bottom: 8px; }
.linklist a { color: var(--ink-strong); text-decoration: none; font-size: .95rem; font-weight: 500; }
.linklist a:hover { color: var(--accent-ink); }

/* CTA panel -------------------------------------------------------------- */

/* React app rendered into the page (the EPS calculator) — templates/sections.html,
   react_app. The mount holds a min-height so the page does not jump while the
   bundle loads; the app's own stylesheet (static/apps/<app>/main.css) styles
   everything inside it. */
.app-mount { min-height: 480px; }
.app-mount__loading, .app-mount__noscript {
  margin: 0; padding: 48px 0; text-align: center; color: var(--ink); font-size: 15px;
}

.band--cta { padding: var(--section-y) 0; }
.band--cta .panel {
  position: relative; isolation: isolate; overflow: hidden;
  border-radius: var(--radius-panel);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
  padding: 90px 40px clamp(120px, 14vw, 170px);
  text-align: center;
  background: var(--hero-ground);
  color: var(--ink);
  box-shadow: 0 26px 60px -40px rgba(var(--shade), .40);
}
/* The same art as the hero, so the page closes on the surface it opened on. */
.band--cta .panel::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(0deg, var(--hero-ground) 0%, transparent 88px),
    var(--art) bottom center / var(--art-size) no-repeat,
    radial-gradient(110% 130% at 50% 132%, var(--glow) 0%, var(--glow-mid) 45%, transparent 75%),
    linear-gradient(180deg, var(--grad-top), var(--grad-bottom));
}
/* A CTA panel with `fx:` (data-fx; static/js/hero-fx.js) gets its canvas
   inside the panel, above the art in ::before (same stacking level, later
   in order) and under the copy, multiplied like the hero's. A soft oval
   holds it to --fx-core of its strength behind the copy. */
.panel__fx {
  position: absolute; inset: 0; z-index: -1; width: 100%; height: 100%;
  mix-blend-mode: multiply; pointer-events: none;
  opacity: 0; transition: opacity .8s ease;
  --fx-core: .3;
  -webkit-mask-image: radial-gradient(ellipse 60% 55% at 50% 45%, rgb(0 0 0 / var(--fx-core)) 50%, #000 100%);
          mask-image: radial-gradient(ellipse 60% 55% at 50% 45%, rgb(0 0 0 / var(--fx-core)) 50%, #000 100%);
}
.panel__fx.is-on { opacity: 1; }
.band--cta h2 { color: var(--ink-strong); max-width: 22ch; margin-inline: auto; }
.band--cta .lead { color: var(--ink); max-width: 70ch; margin-inline: auto; }

/* ------------------------------------------------------------------ form -- */

.band--form { position: relative; isolation: isolate;
  background: var(--sunken); color: var(--ink);
  padding: var(--band-y) 0; border-top: 1px solid var(--line); overflow: hidden; }
/* A blue haze rising off the bottom edge, like the hero's: the page opens and
   closes on the same surface, and the band stops reading as the footer's
   first half. */
.band--form::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    radial-gradient(80% 90% at 50% 130%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 70%);
}
/* The form's heading and intro are a section head too: centred like the rest. */
.band--form h2 { color: var(--ink-strong); text-align: center; }
.band--form .lead { color: var(--ink); text-align: center; margin-inline: auto; }
.form { margin-top: 32px; }
.form__row { display: grid; gap: 0 20px; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.field { margin: 0 0 20px; display: flex; flex-direction: column; gap: 8px; }
.field label { font-size: .8125rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--accent-ink); }
/* Fields are laid ON the band rather than cut into it: white against the
   band's wash, hairline border, accent ring on focus. */
.field input, .field select, .field textarea {
  font: inherit; font-size: .95rem; color: var(--ink-strong);
  background: var(--surface); border: 1px solid var(--line-strong); border-radius: 10px;
  padding: 12px 16px;
  transition: border-color .15s, box-shadow .15s;
}
.field input::placeholder, .field textarea::placeholder { color: color-mix(in srgb, var(--ink) 70%, transparent); }
.field input:hover, .field select:hover, .field textarea:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--accent-ink);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent); }
.field textarea { resize: vertical; }
.form__submit { margin: 8px 0 0; }
.form__privacy { margin-top: 16px; font-size: .85rem; color: var(--ink); }
/* `p:last-child` would take the margin off the second field of every
   .form__row; stacked on a phone, its next label then sat on the field. */
.form .field { margin-bottom: 20px; }
/* The failed-send message (contact-form.js): the site's signal orange, the
   same pale ground as the editor's orange. */
.form__error { margin: 16px 0 0; padding: 14px 18px; border-radius: 10px;
  border-left: 3px solid #d14a0f; background: #f9ece7; color: var(--ink-strong); }
.form--thanks { padding: 32px; border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--accent) 9%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent-ink) 45%, transparent);
  box-shadow: var(--lift); }
.form--thanks h3 { color: var(--ink-strong); }
.form--thanks p { color: var(--ink); }

.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.btn__busy { display: none; }
button.is-busy .btn__label { display: none; }
button.is-busy .btn__busy { display: inline; }
button.is-busy { cursor: progress; }
/* The submit ships disabled (it needs contact-form.js); while sending it is
   disabled too, but then .is-busy keeps it looking like a working button. */
#demo-submit:disabled:not(.is-busy) { opacity: .55; cursor: not-allowed; }
.form__error { margin: 0 0 16px; padding: 12px 16px; border-radius: 10px;
  color: var(--ink-strong);
  background: color-mix(in srgb, var(--danger) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent); }
.form__error[hidden] { display: none; }
.form__note { margin: 0 0 16px; font-size: .95rem; color: var(--ink); }

/* ---------------------------------------------------------------- footer -- */

.site-footer { background: var(--band-ground); color: var(--ink); padding: var(--band-y) 0 40px;
  border-top: 1px solid var(--line); }
.site-footer__grid { display: grid; gap: 40px; grid-template-columns: 1.2fr 1fr 1.1fr; align-items: start; }
.site-footer__heading { margin: 0 0 20px; font-size: 1.25rem; font-weight: var(--heading-weight); color: var(--accent-ink); }
/* A heading with the brand's mark before it, on one line (the organisation
   above the address on both sites, the product above logman.io's links). */
.site-footer__heading--logo { display: flex; align-items: center; gap: 10px; }
.site-footer__mark { height: 24px; width: auto; flex: none; }
.site-footer address { font-style: normal; font-size: .95rem; line-height: 1.9; color: var(--ink-strong); }
.site-footer address a { color: var(--ink-strong); text-decoration: none; }
.site-footer address a:hover { color: var(--accent-ink); }
.site-footer .linklist a { color: var(--ink-strong); }
.site-footer .linklist a:hover { color: var(--accent-ink); }
.site-footer .linklist li { position: relative; padding-left: 16px; }
.site-footer .linklist li::before { content: ""; position: absolute; left: 0; top: .62em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }
/* the outlined "complete ecosystem" box from the reference footer */
.site-footer__panel { border: 1px solid var(--line); border-radius: var(--radius-panel);
  padding: 28px; text-align: center; background: color-mix(in srgb, var(--accent) 6%, var(--band-ground)); }
.site-footer__panel .site-footer__heading { margin-bottom: 20px; }
.site-footer__panel .btn { width: 100%; margin-bottom: 12px; white-space: nowrap; }
.site-footer__panel .btn:last-child { margin-bottom: 0; }

.site-footer__legal { display: flex; flex-wrap: wrap; gap: 8px 24px; align-items: center;
  margin-top: 60px; padding-top: 24px; border-top: 1px solid var(--line-strong);
  font-size: .85rem; color: var(--ink); }
.site-footer__legal p { margin: 0; }
.site-footer__langs { display: flex; gap: 16px; list-style: none; margin: 0 0 0 auto; padding: 0; }
.site-footer__langs a { color: var(--ink); text-decoration: none; }
.site-footer__langs a:hover { color: var(--accent-ink); }
.site-footer__langs a[aria-current="true"] { color: var(--ink-strong); font-weight: 600; }

/* ======================================================================
   REVEAL

   site.js marks the blocks of a page and adds .is-in as each scrolls into
   view. The class that hides them (`.js-reveal`, on <html>) is added BY the
   script and only when the visitor has not asked for reduced motion, so with
   JavaScript off — or motion turned down — nothing is ever hidden.

   Deliberately not applied to .tablewrap: a transformed ancestor would break
   the matrix's sticky column heads.
   ====================================================================== */

.js-reveal [data-reveal] { opacity: 0; transform: translateY(18px); }
.js-reveal [data-reveal].is-in {
  opacity: 1; transform: none;
  transition: opacity .55s ease, transform .55s cubic-bezier(.22,.7,.3,1);
}
/* Items in a row arrive a beat apart, left to right. */
.js-reveal [data-reveal].is-in:nth-child(2) { transition-delay: .07s; }
.js-reveal [data-reveal].is-in:nth-child(3) { transition-delay: .14s; }
.js-reveal [data-reveal].is-in:nth-child(4) { transition-delay: .21s; }

/* ======================================================================
   ROWS OF FOUR

   Four items are a SET, not a list: four of anything must never read as
   3 + 1. The section macros add `row4` wherever a section has exactly four
   items, so this is a property of the content, not of one page. Fixed
   columns rather than auto-fit — auto-fit is what produces 3 + 1.

   Desktop: one row of four. Tablet (up to 1080px): a 2 + 2 grid.
   Phone (700px and below): one column, the items stacked under each other.
   Nothing scrolls sideways.

   `row4--compact` is for short tiles (stats, steps); they hold their four
   columns further down the scale (to 940px) than cards carrying body copy.

   This block sits at the end of the base styles deliberately: `.stats` and
   `.steps` set their own auto-fit tracks at the same specificity, and the
   later rule has to be this one.
   ====================================================================== */

.row4 { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px; }
.row4 > * { min-width: 0; }

@media (max-width: 1080px) {
  .row4:not(.row4--compact) { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
}
@media (max-width: 940px) {
  .row4.row4--compact { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
}
@media (max-width: 700px) {
  .row4:not(.row4--compact), .row4.row4--compact { grid-template-columns: 1fr; }
}

/* ============================================================================
   RESPONSIVE

   Five breakpoints, each with a job:

     1180px  the nav bar no longer fits, so the links become a slide-in panel.
             This was 880px while the header carried a text wordmark; the
             official logo image is ~200px wide, and with it the full bar
             (logo + five links + language + CTA) needs ~1175px in Czech and
             Polish. Below that the logo was squeezed, then vanished.
     1080px  the two widest desktop layouts (split intro, 3-column footer)
             lose a column — laptops and landscape tablets.
      880px  alternating text/figure rows (zigzag) become one column.
      700px  tablet portrait: multi-column card grids that still fit are kept,
             the ones that would squeeze below ~260px are collapsed.
      560px  phone: everything is one column, buttons go full width, the
             header CTA moves into the panel and the comparison table
             becomes a scroller with a sticky feature column.

   Gutters, section rhythm and display type are fluid (clamp) rather than
   stepped, so these breakpoints only ever change LAYOUT, never size.
   ========================================================================== */

/* Tighter display leading is only right once h1 is big. */
@media (min-width: 881px) { h1 { line-height: 0.95; } }

/* --------------------------------------------------------------- 1080px -- */

@media (max-width: 1080px) {
  .split { grid-template-columns: 1fr; }
  .ftable th, .ftable td { padding: 12px 14px; }

  /* Below the desktop breakpoint the matrix no longer fits, so it scrolls
     inside its own box. Two things make that safe: the capability column is
     pinned, so a row never loses its label, and the classic pure-CSS scroll
     shadow marks which side has more table to reach — its two `local` layers
     ride with the content and mask the two `scroll` shadows. */
  .ftable tbody th:not(.ftable__group), .ftable thead th:first-child {
    position: sticky; left: 0; z-index: 1;
    background: var(--surface);
    box-shadow: 1px 0 0 var(--line);
  }
  .tablewrap {
    background-image:
      linear-gradient(to right,  var(--surface), color-mix(in srgb, var(--surface) 0%, transparent)),
      linear-gradient(to left,   var(--surface), color-mix(in srgb, var(--surface) 0%, transparent)),
      linear-gradient(to right,  rgba(var(--shade), .20), rgba(var(--shade), 0)),
      linear-gradient(to left,   rgba(var(--shade), .20), rgba(var(--shade), 0));
    background-position: 0 0, 100% 0, 0 0, 100% 0;
    background-repeat: no-repeat;
    background-size: 24px 100%, 24px 100%, 12px 100%, 12px 100%;
    background-attachment: local, local, scroll, scroll;
  }
  .site-footer__grid { grid-template-columns: 1fr 1fr; }
  /* The ecosystem box would be a lone third column; give it the full row. */
  .site-footer__panel { grid-column: 1 / -1; }
  .zigzag { gap: 44px; }
}

/* ------------------------------------------------- product lockup -- */
/* `header: {product_logo: …}` in site.yaml: a smaller product logo follows
   the company logo after a thin rule — "company | product". The product logo
   is sized to read like a wordmark next to the full logo, not as a second
   logo of equal weight. Below 560px it folds away (the drawer names the
   product at the top of its panel instead). */
.logo-rule { flex: none; width: 1px; height: 34px; background: var(--line-strong); margin: 0 -2px; }
.logo--product { min-width: 0; }
.logo__product { display: block; height: 20px; width: auto; object-fit: contain; object-position: left center; }
/* The product name at the top of the drawer; hidden while the bar is wide. */
.site-nav__product { display: none; }

/* ------------------------------------ 1180px: the nav becomes a drawer -- */

/* Keep this number in step with the matchMedia query in static/js/site.js. */
@media (max-width: 1180px) {
  :root { --header-h: 64px; }

  /* No hero to dissolve into once the bar is this short — keep it solid so
     the burger always has contrast behind it. */
  .site-header, .site-header.at-top { background: var(--band-ground); border-bottom-color: var(--line); }
  /* The bar is solid here, so the blur shows nothing — and it must go: an
     element with backdrop-filter becomes the containing block for its
     position:fixed descendants. With it on, the drawer and its scrim were
     sized against the 64px header instead of the viewport, so the open panel
     collapsed to a ~36px strip under the bar. Applies wherever the drawer
     does, i.e. this whole block. */
  .site-header { -webkit-backdrop-filter: none; backdrop-filter: none; }
  .site-header__inner { position: relative; gap: 12px; flex-wrap: nowrap; }

  /* The invisible checkbox lies exactly over the burger it controls. */
  .navtoggle {
    position: absolute; z-index: 4; margin: 0;
    right: max(var(--pad-x), env(safe-area-inset-right));
    top: 50%; transform: translateY(-50%);
    width: 44px; height: 44px; opacity: 0; pointer-events: auto; cursor: pointer;
    -webkit-appearance: none; appearance: none;
  }
  .navtoggle__btn {
    display: block; position: relative; z-index: 3; order: 3; flex: none;
    width: 44px; height: 44px; border-radius: 10px;
    border: 1px solid var(--line-strong);
  }
  .navtoggle__bars, .navtoggle__bars::before, .navtoggle__bars::after {
    content: ""; position: absolute; left: 12px; width: 18px; height: 2px;
    border-radius: 2px; background: var(--ink-strong);
    transition: transform .2s ease, background-color .1s ease;
  }
  .navtoggle__bars { top: 20px; }
  .navtoggle__bars::before { top: -6px; left: 0; }
  .navtoggle__bars::after  { top:  6px; left: 0; }
  /* Burger folds into a cross while the panel is open. */
  .navtoggle:checked + .navtoggle__btn .navtoggle__bars { background: transparent; }
  .navtoggle:checked + .navtoggle__btn .navtoggle__bars::before { transform: translateY(6px) rotate(45deg); }
  .navtoggle:checked + .navtoggle__btn .navtoggle__bars::after  { transform: translateY(-6px) rotate(-45deg); }
  .navtoggle:focus-visible + .navtoggle__btn { outline: 2px solid var(--accent); outline-offset: 3px; }
  .navtoggle__btn:hover { border-color: var(--accent); }

  .navdrawer__scrim {
    display: block; position: fixed; inset: var(--header-h) 0 0 0; z-index: 1;
    background: rgba(var(--shade), .34);
    opacity: 0; visibility: hidden; transition: opacity .22s ease, visibility .22s ease;
  }
  .navtoggle:checked ~ .navdrawer__scrim { opacity: 1; visibility: visible; }

  /* The panel. `visibility` (not display) so it can animate and so its links
     stay out of the tab order while it is closed. */
  .site-nav {
    position: fixed; z-index: 2; margin: 0;
    top: var(--header-h); right: 0; bottom: 0;
    /* Always leave a strip of the page visible, so the panel reads as an
       overlay and there is somewhere to tap to dismiss it. */
    width: min(360px, calc(100% - 44px));
    flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 0;
    padding: 8px max(var(--pad-x), env(safe-area-inset-right))
             calc(28px + env(safe-area-inset-bottom));
    background: var(--band-ground);
    border-left: 1px solid var(--line);
    box-shadow: -18px 0 44px -20px rgba(var(--shade), .45);
    overflow-y: auto; overscroll-behavior: contain;
    opacity: 0; visibility: hidden; transform: translateX(14px);
    transition: opacity .22s ease, transform .22s ease, visibility .22s ease;
  }
  .navtoggle:checked ~ .site-nav { opacity: 1; visibility: visible; transform: none; }

  .site-nav__list { flex-direction: column; align-items: stretch; gap: 0; }
  .site-nav__list li { display: block; }
  .site-nav__list a {
    display: flex; align-items: center; min-height: 52px;
    padding: 12px 4px; border-radius: 0;
    border-bottom: 1px solid var(--line);
    font-size: .9375rem; white-space: normal;
  }
  .site-nav__list a:hover { background: transparent; color: var(--accent-ink); }
  /* In the panel the current-page marker sits at the left, not underneath. */
  .site-nav__list a[aria-current="page"]::after { left: auto; right: 2px; bottom: auto;
    top: 50%; transform: translateY(-50%); width: 6px; height: 6px; border-radius: 50%; }

  /* Language + CTA stay in the bar at these widths; the burger follows them. */
  .site-nav__end { order: 2; margin-left: auto; flex: none; }
  .langswitch ul { right: 0; }

  /* Lockup sites: the panel opens with the product's name. */
  .site-header--product .site-nav__product { display: block; padding: 16px 4px 12px;
    border-bottom: 1px solid var(--line); color: var(--accent-ink);
    font-size: 1.0625rem; font-weight: 600; letter-spacing: -.01em; }
  .site-header--product .site-nav__product strong { font-weight: 700; }
  .site-header--product .site-nav__product small { display: block; margin-top: 4px;
    font-size: .75rem; font-weight: 400; color: var(--ink); }
}

/* --------------------------------------------- 880px: zigzag rows stack -- */

@media (max-width: 880px) {
  /* Alternating text/figure rows become one column: text first every time. */
  .zigzag { gap: 36px; }
  .zigzag__item { grid-template-columns: 1fr; gap: 24px; }
  .zigzag__item:nth-child(even) .zigzag__text { order: 0; }
  .zigzag__figure { min-height: 190px; }
}

/* Panels shouldn't slide for people who asked for less motion. */
@media (max-width: 1180px) and (prefers-reduced-motion: reduce) {
  .site-nav { transform: none; transition: opacity .01s, visibility .01s; }
}

/* JS adds .nav-open to <html> so the page behind the panel can't scroll.
   Without JS the panel's own overscroll-behavior still contains most of it. */
.nav-open { overflow: hidden; }

/* --------------------------------------------- 700px: tablet portrait -- */

@media (max-width: 700px) {
  /* Zoom the ridge and crop its sides, so it is still a mountain on a phone. */
  :root { --art-size: 190% auto; }

  .zigzag__figure { min-height: 160px; }
  .zigzag__glyph { width: 72px; height: 72px; border-radius: 18px; }
  .zigzag__glyph::before { inset: -16px; border-radius: 26px; }
  .zigzag__glyph::after { inset: -32px; border-radius: 36px; }
  .zigzag h3 { font-size: 1.4rem; }

  .card, .stack__item, .steps__item, .stat { padding: 26px 22px 30px; }
  .split__intro h3 { font-size: 1.3rem; }
  .band--cta .panel { padding: 64px 28px; }
  .timeline { padding-left: 22px; }
  .timeline__item::before { left: -28px; }
  .faq__item summary { padding: 18px 20px; font-size: 1rem; }
  .faq__item p { padding: 0 20px 20px; }

  /* iOS zooms the whole page in when a focused field is under 16px. */
  .field input, .field select, .field textarea { font-size: 16px; }
  /* Stacked fields need a little more air between them than the 20px that
     separates them from their own label. */
  .field, .form .field { margin-bottom: 24px; }
}

/* ------------------------------------------------------- 560px: phone -- */

@media (max-width: 560px) {
  :root { --art-size: 260% auto; }

  /* The bar can't hold a CTA next to the wordmark; the panel gets it instead,
     full width and first thing under the links. */
  .site-nav__end .btn { display: none; }
  .logo__full { height: 38px; }
  .site-nav .site-nav__cta { display: flex; margin-top: 20px; width: 100%; }

  .hero .lead { font-size: 1.0625rem; }
  .hero h1 { max-width: none; }
  /* A long word (pl: "cyberbezpieczeństwa", which also carries a soft hyphen in
     its YAML) must wrap, never run off a 320px screen. */
  .hero h1 { overflow-wrap: break-word; }
  .hero__byline, .hero__note { font-size: .9375rem; }
  .hero__note { margin-top: 32px; }
  .actions { gap: 12px; margin-top: 28px; }
  .actions .btn { width: 100%; white-space: normal; }

  .cards, .grid, .stack, .stats, .steps, .split__items { gap: 16px; }
  .card, .stack__item, .steps__item, .stat { padding: 24px 20px 26px; }
  .band--cta .panel { padding: 48px 22px; border-radius: 14px; }
  .band--cta h2, .band--cta .lead { max-width: none; }

  .form--thanks { padding: 24px 20px; }
  .site-footer__grid { grid-template-columns: 1fr; gap: 32px; }
  .site-footer__legal { margin-top: 40px; }
  .site-footer__langs { margin-left: 0; }
  .site-footer__panel { padding: 24px 20px; }

  .ftable { min-width: 520px; font-size: .9rem; }
  .ftable th, .ftable td { padding: 11px 12px; }
  .ftable thead th { font-size: .7rem; letter-spacing: .06em; }
  .ftable thead th:first-child { min-width: 150px; }
  .ftable tbody th { font-size: .875rem; }
  .ftable tbody th small { font-size: .75rem; }
  .ftable__val { font-size: .75rem; padding: 2px 8px; }
  .ftable tbody th.ftable__group { padding: 10px 12px; font-size: .75rem; letter-spacing: .1em; }
  .tablenotes { margin-top: 20px; gap: 10px; }
}

/* ---------------------------------------- 560px: lockup folds to one logo -- */

@media (max-width: 560px) {
  .logo-rule, .logo--product { display: none; }
}

/* ------------------------------------------------ 380px: small phone -- */

@media (max-width: 380px) {
  .logo__text { font-size: 1.15rem; }
  .logo__full { height: 34px; }
  .btn { padding: 0 20px; }
  .card, .stack__item, .steps__item, .stat { padding: 22px 18px 24px; }
  .faq__item summary { padding: 16px; gap: 12px; }
  .faq__item p { padding: 0 16px 18px; }
}

@media (min-width: 1081px) {
  .tablewrap { overflow: visible; }
  .ftable { min-width: 0; }
  .ftable thead th {
    position: sticky; top: var(--header-h); z-index: 2;
    background: var(--surface);
    /* An inset shadow, not a border: borders on a sticky cell detach from it
       while it is pinned. */
    border-bottom: 0; box-shadow: inset 0 -1px 0 var(--line);
  }
  .ftable thead th:first-child { border-top-left-radius: var(--radius-card); }
  .ftable thead th:last-child { border-top-right-radius: var(--radius-card); }
}

/* A phone held sideways has almost no height: don't spend it all on hero
   padding before the headline appears. */
@media (max-width: 880px) and (max-height: 520px) and (orientation: landscape) {
  .hero { padding-top: calc(var(--header-h) + 28px); padding-bottom: 36px; }
}
