/* =========================================================================
   0. Fonts — self-hosted
   =========================================================================
   Served from this origin, not from Google. Two reasons, both real:

   1. GDPR. A Google Fonts request sends the visitor's IP to Google Ireland
      before they have agreed to anything, which the privacy page then has to
      disclose as a third-country transfer. Self-hosting deletes the transfer
      rather than documenting it.
   2. Delivery. The old chain was preconnect -> googleapis CSS -> gstatic
      woff2: three round trips before a glyph paints, on a phone, over a
      Czech mobile network.

   Subsets: latin + latin-ext only. latin-ext is what carries the Czech
   diacritics, so it is not optional here; vietnamese was dropped. Space
   Grotesk 500 was declared in the old link and used nowhere in this file
   (measured: only 400 and 700 appear), so it is gone too. 10 files, 150 kB.

   font-display: swap is kept from Google's own CSS: the fallback stacks in
   the tokens below are close enough in metrics that a swap reads as a
   correction, not as a flash of a different design.
   ========================================================================= */

/* latin-ext */
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/anton-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* latin */
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/anton-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* latin-ext */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/space-grotesk-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* latin */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/space-grotesk-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* latin-ext */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/space-grotesk-700-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* latin */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/space-grotesk-700-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* latin-ext */
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/space-mono-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* latin */
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/space-mono-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* latin-ext */
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/space-mono-700-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* latin */
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/space-mono-700-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* =========================================================================
   BladeWave — styles.css
   Faithful port of design-source/Bladewave.dc.html + Apple-design taste pass.
   Every colour lives in :root. Nothing below :root carries a raw hex.
   ========================================================================= */

:root {
  /* ---- Palette (exact values from BUILD-BRIEF.md) ---------------------- */
  --bw-bg: #0e0d0c;          /* page background */
  --bw-panel: #14120f;       /* card / input background */
  --bw-cream: #f2ead9;       /* primary text, light section background */
  --bw-pink: #fb2f92;        /* accent */
  --bw-body: #b8b0a2;        /* body copy on dark */
  --bw-muted: #8f887c;       /* labels on dark */
  --bw-faint: #6b6459;       /* fine print */
  --bw-line: #2a2724;        /* hairline borders on dark */
  /* Form-field boundary. --bw-line is 1.31:1 on the page ground and 1.04:1 on
     the field fill, so an unfocused input had no perceivable edge at all
     (WCAG 1.4.11 asks 3:1 of a UI component's boundary). --bw-faint is the
     palette's own next step up and measures 3.41:1 on --bw-bg, so no new
     colour enters; it is used ONLY on the fields, never on section rules. */
  --bw-field-line: var(--bw-faint);
  --bw-stroke: #4a4541;      /* secondary button border */
  --bw-ink: #0e0d0c;         /* text on light sections */
  --bw-ink-body: #4a443c;    /* body copy on cream */
  --bw-faint-safe: var(--bw-muted);  /* fine print on DARK: --bw-faint is 3.32:1 there
                                        (AA needs 4.5); --bw-muted is 5.53:1 and is
                                        already in the palette, so no new hex enters. */
  --bw-ink-faint: #6b6459;   /* fine print on cream — 4.88:1, passes, left alone */
  --bw-cream-2: #e0d5bf;     /* media placeholder fill on cream */
  --bw-pink-body: #3d0a24;   /* body copy on pink — 4.674:1 on --bw-pink, passes */
  --bw-pink-muted: #7a1147;  /* source label ink on pink — 2.964:1, fails AA; unused */
  /* The AA-safe stand-in for --bw-pink-muted. The source separates the two
     on-pink levels by LIGHTNESS (L* 26.7 vs 11.5) and that route is closed:
     4.5:1 is the floor and the body already sits at 4.674:1, so the whole
     legal window is L* 11.5–12.9 — 1.4 units, invisible. The separation has
     to come from chroma instead. #4b0011 is the best value there:
       4.531:1 on #fb2f92          (AA pass, body 4.674:1)
       dE2000 10.14 vs #3d0a24     (the body it must not merge with)
       dE2000 23.42 vs #0e0d0c     (the name/price ink it must not merge with)
     and it keeps the source's ORDER — fg 5.477 > body 4.674 > muted 4.531 —
     so the labels still recede. Going darker instead reaches dE 9.6 but
     inverts that order (5.488 > body) and closes on the near-black name and
     price (dE 13.6). Measured, not guessed. */
  --bw-pink-muted-safe: #4b0011;

  /* ---- Channel tokens: single source of truth for the alpha variants --- */
  --bw-bg-rgb: 14 13 12;
  --bw-pink-rgb: 251 47 146;

  --bw-nav-bg: rgb(var(--bw-bg-rgb) / 0.72);
  --bw-nav-edge: linear-gradient(to bottom, rgb(var(--bw-bg-rgb) / 0.85), transparent);
  --bw-pink-glow: rgb(var(--bw-pink-rgb) / 0.35);
  --bw-pink-ring: rgb(var(--bw-pink-rgb) / 0.28);
  --bw-slot-fill: rgba(127, 127, 127, 0.08); /* image-slot empty state, per brief */

  /* ---- Type families -------------------------------------------------- */
  --bw-font-display: 'Anton', 'Arial Narrow', 'Haettenschweiler', Impact, sans-serif;
  --bw-font-body: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --bw-font-mono: 'Space Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New', monospace;
  --bw-font-system: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  /* ---- Type scale (derived from the source's inline clamp() values) ---- */
  --bw-fs-hero: clamp(64px, 11vw, 170px);
  --bw-fs-display: clamp(40px, 6vw, 90px);
  --bw-fs-display-xl: clamp(52px, 6.5vw, 110px);
  --bw-fs-display-sm: clamp(36px, 5vw, 72px);
  --bw-fs-service: clamp(28px, 3.6vw, 52px);
  --bw-fs-stat: clamp(44px, 6vw, 84px);
  --bw-fs-price: clamp(28px, 2.4vw, 38px);
  --bw-fs-plan: 25px;
  --bw-fs-num: 60px;
  --bw-fs-logo: 24px;
  --bw-fs-lead: 19px;
  --bw-fs-lead-2: 18px;
  --bw-fs-lead-3: 17px;
  --bw-fs-body: 16px;
  --bw-fs-body-2: 15px;
  --bw-fs-item: 14.5px;
  --bw-fs-mono: 14px;
  --bw-fs-mono-2: 13px;
  --bw-fs-mono-3: 12.5px;
  --bw-fs-mono-4: 12px;
  --bw-fs-fine: 11.5px;
  --bw-fs-cap: 13px;

  /* ---- Tracking & leading (Apple rule 15: size-specific tracking) ------ */
  /* The source declares tracking in exactly five places, and each one is
     honoured verbatim below or at its rule:
       nav logo      1px    -> .bw-nav__logo
       h1            0      -> --bw-track-hero
       marquee       1px    -> .bw-marquee__track
       sluzby h3     0.5px  -> .bw-service__title
       proces h3     0.5px  -> .bw-step__title
     Everywhere else the source declares nothing, so those headings keep the
     skill's negative display tracking. (The h1's -0.025em was also wrong on
     its own terms: it ate the sidebearings the 2px stroke on "NEJDE" needs.) */
  --bw-track-hero: 0em;
  --bw-track-display: -0.02em;
  --bw-track-display-sm: -0.01em;
  --bw-track-body: 0em;
  /* The source's 0.92 was a single-line-per-colour value and the h1 is two
     lines in Czech: measured at 1440, the pink caron of Ř in PŘESKOČIT is
     drawn straight through the cream K of KTERÝ, and the comma of OBSAH,
     lands inside the outlined NEJDE. The mobile block already carried 1.15
     for exactly this reason; one value now covers every width, so the
     override below 760px is gone rather than duplicated. */
  --bw-lh-hero: 1.15;
  --bw-lh-display: 0.95;      /* single-line h2s: leading is invisible there */
  /* Two-line Anton headings whose leading the *build* invented (the source
     declares none, so they rendered at `normal` ≈ 1.51). Anton's caron/acute
     band reaches ~1.10em above the next baseline in Czech caps, so 0.95 makes
     Ý collide with Ý. 1.15 is the tightest value that clears at every width
     (measured: 1.10 = break-even, 1.15 = 1.8–3.6px of air). */
  --bw-lh-display-multi: 1.15;
  /* Ů is taller than an acute: its ring sits a further ~0.10em above the cap
     line, and at 1.15 the Ů of ÚDAJŮ is drawn inside the A of OCHRANA on the
     privacy page's h1. 1.25 is the first value that opens real air there. */
  --bw-lh-display-ring: 1.25;
  --bw-lh-tight: 1.1;
  --bw-lh-snug: 1.45;
  --bw-lh-body: 1.55;
  --bw-lh-loose: 1.6;

  /* ---- Spacing / layout ----------------------------------------------- */
  --bw-gutter: 40px;
  /* Raised below when the browser reports a real inset. Declared here as the
     plain value so nothing depends on env() being understood. */
  --bw-gutter-sm: 20px;
  --bw-max: 1240px;
  --bw-max-narrow: 900px;
  --bw-col-gap: 70px;
  --bw-sec-y: 100px;
  --bw-sec-y-lg: 110px;
  --bw-sec-y-sm: 90px;
  --bw-hair: 1px;
  --bw-rule: 2px;
  --bw-diag: 3vw;             /* Prace section diagonal */
  --bw-card: 270px;           /* work card width */
  /* Room for the rotated band. The bleed of a -1.2deg rotation grows with the
     viewport (~0.0107 x width), so a fixed 36px allowance started slicing the
     band's corners and its 3px border above ~3300px. Fluid clears it at every
     width. */
  --bw-marquee-bleed: calc(1.2vw + 12px);

  /* ---- Motion ---------------------------------------------------------- */
  --bw-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --bw-t-press: 100ms;
  --bw-t-hover: 150ms;
  --bw-t-edge: 250ms;

  /* ---- Focus ----------------------------------------------------------
     One ring everywhere. Only the offset varies, and only for one reason:
     +3px by default, negative where a clip-path or overflow:hidden would
     eat it (notched buttons -6px, carousel viewport -4px). */
  --bw-focus: 2px solid var(--bw-pink);
  --bw-focus-offset: 3px;

  /* ---- Shapes ---------------------------------------------------------- */
  --bw-notch-sm: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%);
  --bw-notch-lg: polygon(0 0, calc(100% - 14px) 0, 100% 14px, 100% 100%, 0 100%);

  color-scheme: dark;
}

/* =========================================================================
   1. Base
   ========================================================================= */

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

/* The hidden attribute has to win. It is an attribute selector, so a class
   that sets display -- .bw-footer__link { display: inline-block } is the one
   that caught us -- has equal specificity and later source order, and the
   element renders anyway. That is how the cookie settings button shipped
   VISIBLE and inert on all sixteen pages while `el.hidden` cheerfully
   reported true: the property was set, the box was still 135px wide.
   Test computed display, not the property, and keep this rule above the
   components that would otherwise outrank it. */
[hidden] { display: none !important; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: 86px;   /* clears the sticky nav on anchor jumps */
  overflow-x: clip;           /* belt-and-braces: no horizontal scroll, sticky safe */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body,
.bw-body {
  margin: 0;
  min-height: 100vh;
  background: var(--bw-bg);
  color: var(--bw-cream);
  font-family: var(--bw-font-body);
  font-size: var(--bw-fs-body);
  line-height: var(--bw-lh-body);
  letter-spacing: var(--bw-track-body);
  overflow-wrap: break-word;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a {
  color: var(--bw-cream);
  text-decoration: none;
}

a:focus-visible,
summary:focus-visible,
button:focus-visible,
[tabindex]:focus-visible {
  outline: var(--bw-focus);
  outline-offset: var(--bw-focus-offset);
}

::selection {
  background: var(--bw-pink);
  color: var(--bw-ink);
}

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

.bw-main:focus { outline: none; }

/* =========================================================================
   2. Utilities
   ========================================================================= */

.bw-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
  text-transform: none;
}

.bw-skip {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 100;
  background: var(--bw-pink);
  color: var(--bw-ink);
  font-family: var(--bw-font-mono);
  font-weight: 700;
  font-size: var(--bw-fs-mono);
  padding: 14px 20px;
  transform: translateY(calc(-100% - 24px));
  transition: transform var(--bw-t-edge) var(--bw-ease);
}

.bw-skip:focus-visible {
  transform: translateY(0);
  outline: 2px solid var(--bw-cream);
  outline-offset: 3px;
  color: var(--bw-ink);
}

/* =========================================================================
   3. Keyframes and scroll reveal
   ========================================================================= */

@keyframes bw-marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

@keyframes bw-hero-in {
  from { opacity: 0; transform: translateY(40px); }
  to { opacity: 1; transform: translateY(0); }
}

[data-reveal] {
  opacity: 0;
  transform: translateY(30px);   /* source value, uniform */
  transition: opacity 0.7s ease, transform 0.8s var(--bw-ease);
  will-change: opacity, transform;
}

[data-reveal].bw-in {
  opacity: 1;
  transform: translateY(0);
  will-change: auto;
}

/* Distance is the source's, uniformly: `translateY(30px)` for every
   [data-reveal]. An earlier pass scaled it per element (18px / 12px) on the
   theory that a big block should travel less; the source says otherwise and
   the source wins.

   The grid arrives as one plate on that 30px, and the six plans then land in
   reading order on top of it. The stagger is the only addition — children
   only, no extra markup, and the observer still watches the container exactly
   as the source specifies. The child offset stays 16px because it rides on
   the container's 30px rather than replacing it: peak travel is 46px, but
   only at t=0 where both opacities are still 0. Measured through the
   transition, the first frame with any ink on screen (t=140ms, container
   opacity 0.38) shows 11.9-25.3px of remaining travel and the last plan is
   home by 560ms. The cards also slide against the cream 2px grid lines, so a
   larger child travel would open visible cream bands between them. */
.bw-pricing__grid > .bw-plan {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.5s ease, transform 0.55s var(--bw-ease);
}

.bw-pricing__grid.bw-in > .bw-plan {
  opacity: 1;
  transform: none;
}

.bw-pricing__grid.bw-in > .bw-plan:nth-child(1) { transition-delay: 0ms; }
.bw-pricing__grid.bw-in > .bw-plan:nth-child(2) { transition-delay: 55ms; }
.bw-pricing__grid.bw-in > .bw-plan:nth-child(3) { transition-delay: 110ms; }
.bw-pricing__grid.bw-in > .bw-plan:nth-child(4) { transition-delay: 165ms; }
.bw-pricing__grid.bw-in > .bw-plan:nth-child(5) { transition-delay: 220ms; }
.bw-pricing__grid.bw-in > .bw-plan:nth-child(6) { transition-delay: 275ms; }

/* ---- No-JS / missing-script guard for the reveal --------------------------
   The two rules above hide their targets at `opacity: 0` and hand the reveal
   to main.js. That is a single point of failure: without the script nothing
   ever adds `.bw-in`, and the six plans plus five section headings stay
   invisible for good — measured, 900px of black where the ceník should be.
   Two independent guards close it, and neither touches the working path.

   1. Scripting switched off. `(scripting: none)` is the exact question, and a
      browser too old to know the feature simply never matches it — so nothing
      regresses there, it just falls back to guard 2.

   2. Script enabled but never arrived (404, blocked, parse error). CSS cannot
      ask that directly, so it asks for proof of life instead: main.js writes
      `#bw-nav-edge`'s inline opacity and `#bw-sticky`'s aria-hidden on its
      first synchronous pass, and adds `.bw-in` from the observer. Any one of
      those three appearing in the document cancels the fail-safe animation;
      if none appears, it fades everything in at 1.6s — comfortably after
      main.js's own 1200ms observer fail-safe, so a healthy page never sees it.
      Both halves live behind `@supports selector(:has())`, because a browser
      that cannot evaluate the cancel must not be given the animation either. */
@media (scripting: none) {
  [data-reveal],
  [data-reveal].bw-in,
  .bw-pricing__grid > .bw-plan,
  .bw-pricing__grid.bw-in > .bw-plan {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0s;
  }
}

@keyframes bw-reveal-failsafe {
  to { opacity: 1; transform: none; }
}

@supports selector(html:has(*)) {
  [data-reveal],
  .bw-pricing__grid > .bw-plan {
    animation: bw-reveal-failsafe 0.6s var(--bw-ease) 1.6s forwards;
  }

  html:has(#bw-nav-edge[style], #bw-sticky[aria-hidden], [data-reveal].bw-in)
  :is([data-reveal], .bw-pricing__grid > .bw-plan) {
    animation: none;
  }
}

/* =========================================================================
   4. Buttons
   ========================================================================= */

/* Inline flow, not flex. In the source a button is one run of text and the
   literal space before the arrow is what separates them; here the arrow was a
   sibling FLEX ITEM spaced by `gap: 0.55em` instead, which broke twice.

   1. The `.bw-sr-only` span is `position: absolute`, so it is not a flex item
      itself — but it does split the markup's word space off into its own
      anonymous flex item. Chromium paints nothing for a whitespace-only
      anonymous item, yet still counts it for `gap`, so the six plan CTAs got
      TWO gaps: 15.38px label-to-arrow against the source's 8.55px.
   2. On every other button the space is the trailing whitespace of the label's
      own text run, and trailing whitespace collapses at the end of a line box.
      So the space never rendered anywhere: copying a button gave
      "REZERVOVAT CALL↗", not the source's "REZERVOVAT CALL ↗".

   One inline run fixes both — the word space is the spacing, and it is real
   text. Vertical centring came from `align-items`, horizontal from
   `justify-content`; symmetric padding and `text-align` do it now. */
.bw-btn {
  display: inline-block;
  text-align: center;
  min-height: 44px;                 /* a11y: tap target */
  font-family: var(--bw-font-mono);
  font-weight: 700;
  font-size: var(--bw-fs-mono);
  /* Space Mono's own `normal` leading is ~1.45; the source relies on it plus
     the stated padding for every button height. */
  line-height: 1.45;
  text-transform: uppercase;
  text-decoration: none;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  transition:
    transform var(--bw-t-press) ease-out,
    background-color var(--bw-t-hover) ease-out,
    border-color var(--bw-t-hover) ease-out,
    box-shadow var(--bw-t-hover) ease-out,
    filter var(--bw-t-hover) ease-out,
    color var(--bw-t-hover) ease-out,
    opacity var(--bw-t-hover) ease-out;
}

.bw-btn:active { transform: scale(0.97); }   /* Apple rule 1: pointer-down feedback */

.bw-btn__arrow {
  line-height: 1;
}

.bw-btn--pink {
  background: var(--bw-pink);
  color: var(--bw-ink);
}

.bw-btn--outline {
  border: 1.5px solid var(--bw-stroke);
  color: var(--bw-cream);
}

.bw-btn--sm { padding: 12px 24px; }

.bw-btn--lg {
  padding: 20px 36px;
  font-size: var(--bw-fs-body);
}

/* The portfolio block's CTA carries neither --sm nor --lg, so it would render
   at zero padding. Sized between the two: it is a section-level primary, not
   the page's headline action. */
.bw-works__offer-cta { padding: 17px 30px; }

/* Notched buttons clip their own outline, so the focus ring is drawn inside. */
.bw-nav__cta,
.bw-hero__cta,
.bw-form__submit,
.bw-sticky__btn,
.bw-works__offer-cta,
.bw-cta__btn { clip-path: var(--bw-notch-lg); }

/* Text-only button — no arrow span, no sr-only — so a flex box is safe here:
   it cannot swallow a literal word space the way it would on the arrow
   buttons. Needed because `min-height: 44px` leaves slack that inline-block
   drops entirely below the text: at <=354px the 12.5px label has an 18.125px
   line box in a 20px content box and sat 1.00px high inside its own pill. */
.bw-nav__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  clip-path: var(--bw-notch-sm);
}

/* Same ring, ink instead of pink (a pink ring on a pink fill is invisible),
   and pulled inside because the notch clips anything outside the box. */
.bw-nav__cta:focus-visible,
.bw-hero__cta:focus-visible,
.bw-form__submit:focus-visible,
.bw-sticky__btn:focus-visible,
.bw-works__offer-cta:focus-visible,
.bw-cta__btn:focus-visible {
  outline: var(--bw-focus);
  outline-color: var(--bw-ink);
  outline-offset: -6px;
}

/* =========================================================================
   5. Header / nav
   ========================================================================= */

.bw-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bw-nav-bg);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  backdrop-filter: blur(20px) saturate(140%);
}

.bw-header__edge {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -18px;
  height: 18px;
  background: var(--bw-nav-edge);
  opacity: 0;
  transition: opacity var(--bw-t-edge) ease-out;
  pointer-events: none;
}

.bw-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;   /* last resort: stack rather than clip the CTA off-screen */
  gap: 20px;
  row-gap: 10px;
  padding: 18px var(--bw-gutter);
}

.bw-nav__logo {
  /* inline-flex, not inline-block: the home link is a real tap target and it
     measured 105x40 on an iPhone SE, four pixels under the 44px floor. Flex
     centres the wordmark inside the taller box instead of the extra height
     falling below the text, which is what inline-block does. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-logo);
  line-height: 1.2;
  letter-spacing: 1px;
  color: var(--bw-cream);
  padding: 8px 0;
  margin: -8px 0;
  transition: color var(--bw-t-hover) ease-out;
}

.bw-nav__star { color: var(--bw-pink); }

.bw-nav__menu {
  display: flex;
  align-items: center;
  gap: 30px;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono);
}

.bw-nav__item { display: flex; }

.bw-nav__link {
  display: inline-block;
  color: var(--bw-body);
  padding: 13px 6px;          /* a11y: 44px tall hit area, layout unchanged */
  margin: -13px -6px;
  transition: color var(--bw-t-hover) ease-out;
}

/* =========================================================================
   6. Hero
   ========================================================================= */

.bw-hero {
  position: relative;
  padding: 90px var(--bw-gutter) 70px;
}

.bw-hero__inner {
  position: relative;
  max-width: var(--bw-max);
  margin: 0 auto;
}

.bw-hero__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-hero);
  line-height: var(--bw-lh-hero);
  letter-spacing: var(--bw-track-hero);
  text-transform: uppercase;
  animation: bw-hero-in 0.9s var(--bw-ease) both;
}

.bw-hero__stroke {
  color: transparent;
  -webkit-text-stroke: 2px var(--bw-cream);
}

.bw-hero__accent { color: var(--bw-pink); }

.bw-hero__foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 40px;
  margin-top: 54px;
  flex-wrap: wrap;
  animation: bw-hero-in 0.9s 0.25s var(--bw-ease) both;
}

.bw-hero__lead {
  font-size: var(--bw-fs-lead);
  color: var(--bw-body);
  line-height: var(--bw-lh-body);
  max-width: 440px;
}

.bw-hero__actions {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}

/* =========================================================================
   7. Marquee
   ========================================================================= */

/* The band is rotated and scaled, so it bleeds past its box. The wrapper
   clips that bleed horizontally while padding + equal negative margin keep
   the vertical rhythm byte-identical to the source. */
.bw-marquee {
  overflow: hidden;
  padding-block: var(--bw-marquee-bleed);
  margin-block: calc(-1 * var(--bw-marquee-bleed));
}

.bw-marquee__band {
  background: var(--bw-pink);
  color: var(--bw-ink);
  overflow: hidden;
  padding: 14px 0;
  transform: rotate(-1.2deg) scale(1.02);
  border-top: 3px solid var(--bw-ink);
  border-bottom: 3px solid var(--bw-ink);
}

.bw-marquee__track {
  display: flex;
  width: max-content;
  animation: bw-marquee 22s linear infinite;
  font-family: var(--bw-font-display);
  font-size: var(--bw-fs-logo);
  text-transform: uppercase;
  letter-spacing: 1px;
  white-space: nowrap;
  will-change: transform;
}

.bw-marquee__item { padding-right: 24px; }

/* =========================================================================
   8. Stats
   ========================================================================= */

.bw-stats { padding: 70px var(--bw-gutter) 0; }

.bw-stats__inner {
  max-width: var(--bw-max);
  margin: 0 auto;
}

.bw-stats__list {
  display: flex;
  gap: 48px;
  flex-wrap: wrap;
  align-items: baseline;
}

.bw-stat {
  display: flex;
  align-items: baseline;
  gap: 16px;
}

.bw-stat__value {
  font-family: var(--bw-font-display);
  font-size: var(--bw-fs-stat);
  line-height: var(--bw-lh-display);
  letter-spacing: var(--bw-track-display);
}

.bw-stat--accent .bw-stat__value { color: var(--bw-pink); }
.bw-stat--cream .bw-stat__value { color: var(--bw-cream); }

.bw-stat__label {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono);
  color: var(--bw-muted);
  text-transform: uppercase;
}

/* =========================================================================
   9. Sluzby
   ========================================================================= */

.bw-services { padding: var(--bw-sec-y-sm) var(--bw-gutter) var(--bw-sec-y); }

.bw-services__inner {
  max-width: var(--bw-max);
  margin: 0 auto;
}

.bw-services__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-display);
  line-height: var(--bw-lh-display);
  letter-spacing: var(--bw-track-display);
  margin: 0 0 48px;
  text-transform: uppercase;
}

.bw-services__accent { color: var(--bw-pink); }

.bw-service {
  display: grid;
  grid-template-columns: 90px 1fr 1fr;
  gap: 24px;
  align-items: baseline;
  padding: 34px 0;
  border-top: var(--bw-hair) solid var(--bw-line);
  transition: background-color 180ms ease-out;
}

.bw-service__num {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-body-2);
  color: var(--bw-muted);
}

.bw-service__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-service);
  line-height: var(--bw-lh-display);
  /* Source-declared: `letter-spacing: 0.5px` on the sluzby h3. An earlier pass
     overwrote it with --bw-track-display-sm (-0.5184px at 51.84px) on the
     strength of a comment claiming the h1 was the only heading with declared
     tracking — it is not. Positive, as designed. */
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

.bw-service__text {
  font-size: var(--bw-fs-body);
  color: var(--bw-body);
  line-height: var(--bw-lh-body);
}

.bw-services__rule { border-top: var(--bw-hair) solid var(--bw-line); }

/* =========================================================================
   10. Prace (carousel)
   ========================================================================= */

.bw-works {
  position: relative;
  background: var(--bw-cream);
  color: var(--bw-ink);
  padding: var(--bw-sec-y) var(--bw-gutter);
  clip-path: polygon(0 0, 100% var(--bw-diag), 100% 100%, 0 100%);
  margin-top: calc(-1 * var(--bw-diag));
}

.bw-works a { color: var(--bw-ink); }

.bw-works__inner {
  max-width: var(--bw-max);
  margin: 0 auto;
}

.bw-works__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin: 30px 0 60px;
  flex-wrap: wrap;
}

.bw-works__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-display);
  line-height: var(--bw-lh-display);
  letter-spacing: var(--bw-track-display);
  text-transform: uppercase;
}

.bw-works__stroke {
  color: transparent;
  -webkit-text-stroke: 2px var(--bw-ink);
}

.bw-works__lead {
  font-size: var(--bw-fs-lead-3);
  color: var(--bw-ink-body);
  max-width: 340px;
  line-height: 1.5;
}

.bw-works__hint {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-2);
  color: var(--bw-ink-faint);
  margin: 0 0 18px;
}

/* Native scrolling is the DEFAULT, and main.js takes it away once it is
   actually running (.bw-js-drag). @media (scripting: none) only covers
   JavaScript switched off — it does not cover the case this stylesheet already
   builds a reveal fail-safe for: script enabled but never arrived, because the
   file 404s, a blocker ate it or it threw on parse. In that state the old rule
   left four of six cards unreachable by any gesture while the page still said
   "TAHNETE" and promised arrow keys. Default to something that works, then
   upgrade. */
.bw-works__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  margin: 0 calc(-1 * var(--bw-gutter));
  padding: 20px var(--bw-gutter) 40px;
  /* The drag is registered here, not on the track, so the padding strips grab
     too — they were dead surface before. */
  user-select: none;
  -webkit-user-select: none;
}

/* main.js sets this on <html> the moment the drag handler is bound. Only then
   is it safe to hand scrolling over to the pointer code. */
.bw-js-drag .bw-works__viewport {
  overflow-x: hidden;
  scroll-snap-type: none;
  cursor: grab;
  touch-action: pan-y;
}

/* Above the container width the -40px bleed no longer reaches the page edges:
   the track was sliced mid-card 300px short of the right edge at 1920 with a
   band of empty cream after it, instead of continuing off-screen. Bleed to the
   real edges and put the same amount back as padding, so the cards still start
   on the container's grid line. main.js measures this padding rather than
   assuming 80px, so the drag bounds follow. */
@media (min-width: 1321px) {
  .bw-works__viewport {
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    padding-left: calc(50vw - 50%);
    padding-right: calc(50vw - 50%);
  }
}

/* The viewport is tabbable (tabindex="0") and overflow:hidden, so the ring is
   drawn inside. Pink on this section's cream is only 2.96:1 (1.4.11 needs 3);
   ink is 16.22:1 — the same swap the accent plan's CTA already makes. */
.bw-works__viewport:focus-visible {
  outline: var(--bw-focus);
  outline-color: var(--bw-ink);
  outline-offset: -4px;
}

.bw-works__track {
  display: flex;
  gap: 26px;
  width: max-content;
  will-change: transform;
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}

.bw-work {
  flex: 0 0 var(--bw-card);
  width: var(--bw-card);
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.bw-work--shift { transform: translateY(34px); }

.bw-work__media {
  position: relative;
  aspect-ratio: 9 / 16;
  overflow: hidden;
  background: var(--bw-cream-2);
  border: var(--bw-rule) solid var(--bw-ink);
}

/* Reserved for the real clips. The markup comment in index.html tells the
   owner to drop an <a class="bw-work__link"><video class="bw-work__video">
   into .bw-work__media once references exist; these two rules are what makes
   that paste land correctly — the link fills the 9:16 frame, the video covers
   it without letterboxing. (.bw-work__views and .bw-work__brand went with the
   invented view counts and brand names the audit removed.) */
.bw-work__link {
  position: absolute;
  inset: 0;
  display: block;
}

.bw-work__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.bw-work__meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}

.bw-work__label {
  font-weight: 700;
  font-size: var(--bw-fs-lead-3);
}

.bw-work__type {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-4);
  color: var(--bw-ink-faint);
  text-transform: uppercase;
}

/* ---- Honest portfolio block ----------------------------------------------
   Sits under the carousel on the cream section, so every colour here is the
   on-cream set: --bw-ink for display ink, --bw-ink-body for copy,
   --bw-ink-faint for the kicker. Pink is deliberately absent as TEXT (2.96:1
   on cream, below AA) and present only as the CTA's fill, where the ink label
   carries the contrast — the same swap .bw-works__viewport:focus-visible and
   the accent plan's CTA already make. */
.bw-works__offer {
  margin-top: 26px;
  padding: 34px 36px;
  border: var(--bw-rule) solid var(--bw-ink);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 48px;
  row-gap: 24px;
}

.bw-works__offer-kicker {
  grid-column: 1;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-3);
  color: var(--bw-ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0 0 10px;
}

.bw-works__offer-title {
  grid-column: 1;
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-price);
  line-height: var(--bw-lh-tight);
  letter-spacing: var(--bw-track-display-sm);
  text-transform: uppercase;
  margin: 0 0 14px;
}

.bw-works__offer-text {
  grid-column: 1;
  font-size: var(--bw-fs-lead-3);
  color: var(--bw-ink-body);
  line-height: var(--bw-lh-body);
  max-width: 56ch;
  margin: 0;
}

/* Column 2, spanning the kicker/title/text rows: the button sits beside the
   block, vertically centred, and drops under it when the grid collapses at
   760px. `span 3` rather than `1 / -1` — the grid has no EXPLICIT rows, so
   -1 resolves to line 1 and the button rode up level with the kicker. */
.bw-works__offer-cta {
  grid-column: 2;
  grid-row: 1 / span 3;
  align-self: center;
  justify-self: end;
  white-space: nowrap;
}

/* ---- No-JS carousel -------------------------------------------------------
   The viewport is `overflow: hidden` and moved only by main.js's transform, so
   with scripting off four to five of the six cards were unreachable at every
   width — while the region still advertised drag and arrow/Home/End keys that
   did nothing. Hand it back to the browser's own scroller. The viewport already
   carries tabindex="0", so ArrowLeft/Right/Home/End act on it natively, and
   touch gets real horizontal panning once touch-action stops blocking it.

   Declared HERE, after the rules it overrides: `overflow: hidden` above is the
   shorthand and a media query adds no specificity, so the same block placed
   with the other (scripting: none) rules in §3 lost to it. */
@media (scripting: none) {
  /* The viewport's scrolling defaults already hold here; what still has to be
     undone is the drag-only affordance the track carries. */

  .bw-works__track {
    touch-action: auto;
    cursor: auto;
  }

  .bw-work { scroll-snap-align: start; }
}

/* =========================================================================
   11. Image placeholder (static equivalent of image-slot.js empty state)
   ========================================================================= */

.bw-slot {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12px;
  background: var(--bw-slot-fill);
}

.bw-slot::before {
  content: "";
  position: absolute;
  inset: 0;
  border: 1.5px dashed currentColor;
  opacity: 0.35;
  pointer-events: none;
}

.bw-slot__cap {
  position: relative;
  font-family: var(--bw-font-system);
  font-size: var(--bw-fs-cap);
  line-height: 1.3;
  text-align: center;
  opacity: 0.75;
}

/* =========================================================================
   12. Proces
   ========================================================================= */

.bw-process { padding: var(--bw-sec-y-lg) var(--bw-gutter); }

.bw-process__inner {
  max-width: var(--bw-max);
  margin: 0 auto;
}

.bw-process__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-display);
  line-height: var(--bw-lh-display-multi);   /* two lines, Czech diacritics */
  letter-spacing: var(--bw-track-display);
  margin: 0 0 64px;
  text-transform: uppercase;
}

.bw-process__accent { color: var(--bw-pink); }

/* No styling on the arrow glyphs. Anton carries no U+2192 / U+2197, so the
   browser substitutes from the fallback stack — and that substitution IS the
   design: a long, thin diagonal with an open head, drawn at the heading's own
   86.4px / 93.6px. An earlier pass re-set both in 0.72em Space Mono with a
   0.1em text-stroke; measured against the source render that fused the shaft
   into the head and the glyph stopped reading as an arrow. Reverted. */

.bw-process__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 0;
}

.bw-step {
  border-left: var(--bw-hair) solid var(--bw-line);
  padding: 8px 28px 8px 24px;
}

/* The rules separate the steps; they do not precede the group. Zeroing the
   first column's rule and inset puts "01" on the same grid line as the
   headline above it and as every other section's content. */
.bw-step:first-child {
  border-left: 0;
  padding-left: 0;
}

.bw-step__num {
  display: block;
  font-family: var(--bw-font-display);
  font-size: var(--bw-fs-num);
  color: transparent;
  -webkit-text-stroke: 1.5px var(--bw-pink);
  line-height: 1;
  /* Outlined type needs POSITIVE tracking: the 1.5px stroke grows 0.75px
     outward on each side and eats the sidebearings. At -0.02em the 0 and the
     1 of "01" fuse into one shape; +0.02em restores a 2.7px gap. */
  letter-spacing: 0.02em;
  margin-bottom: 20px;
}

.bw-step__title {
  font-size: 20px;
  font-weight: 700;
  line-height: var(--bw-lh-tight);
  margin: 0 0 10px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.bw-step__text {
  font-size: var(--bw-fs-body-2);
  color: var(--bw-body);
  line-height: var(--bw-lh-loose);
}

/* =========================================================================
   12b. CTA bands  (.bw-cta--mid after #proces, .bw-cta--faq closing the FAQ)
   -------------------------------------------------------------------------
   Both bands share one centred stack; only the frame differs. The mid band is
   a section of its own and takes the page gutter plus a --bw-panel ground, so
   the long dark run between the cream PRÁCE section and the ceník is broken
   by a surface change rather than by more empty space. The FAQ band is a box
   inside .bw-faq__inner, which already carries the gutter — giving it one too
   would double it.
   ========================================================================= */

.bw-cta__inner {
  max-width: 720px;
  margin: 0 auto;
  text-align: center;
}

.bw-cta__kicker {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-3);
  color: var(--bw-pink);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0 0 18px;
}

.bw-cta__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-service);
  line-height: var(--bw-lh-display-multi);   /* wraps to 2–3 lines, Czech diacritics */
  letter-spacing: var(--bw-track-display);
  text-transform: uppercase;
  margin: 0 0 18px;
}

.bw-cta__text {
  font-size: var(--bw-fs-lead-2);
  color: var(--bw-body);
  line-height: var(--bw-lh-loose);
  max-width: 560px;
  margin: 0 auto 30px;
}

.bw-cta__btn { display: inline-block; }

.bw-cta__note {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-4);
  color: var(--bw-faint-safe);
  margin: 18px 0 0;
}

.bw-cta--mid {
  border-top: var(--bw-hair) solid var(--bw-line);
  background: var(--bw-panel);
  padding: var(--bw-sec-y-sm) var(--bw-gutter);
}

/* No bottom border: .bw-pricing opens with its own border-top and the two
   would stack into a 2px double rule. */

.bw-cta--faq {
  margin-top: 56px;
  padding: 44px 36px;
  border: 1.5px solid var(--bw-line);
  background: var(--bw-panel);
}

/* One step down from the mid band: this one is a sub-block of the FAQ, not a
   section, and it must not outrank "ČASTÉ OTÁZKY" above it. */
.bw-cta--faq .bw-cta__title { font-size: var(--bw-fs-price); }

/* =========================================================================
   13. Cenik
   ========================================================================= */

.bw-pricing {
  border-top: var(--bw-hair) solid var(--bw-line);
  padding: var(--bw-sec-y) var(--bw-gutter);
}

.bw-pricing__inner {
  max-width: var(--bw-max);
  margin: 0 auto;
}

.bw-pricing__head { margin-bottom: 64px; }

.bw-pricing__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-display);
  line-height: var(--bw-lh-display);
  letter-spacing: var(--bw-track-display);
  margin: 0 0 16px;
  text-transform: uppercase;
}

.bw-pricing__stroke {
  color: transparent;
  -webkit-text-stroke: 2px var(--bw-cream);
}

.bw-pricing__lead {
  font-size: var(--bw-fs-lead-3);
  color: var(--bw-body);
  max-width: 520px;
  line-height: 1.5;
}

/* ---- Risk reversal strip -------------------------------------------------
   Four guarantees that hold across all six plans, so they are stated once
   above the grid instead of six times inside it. Deliberately quieter than a
   plan card — a hairline panel, mono label, no display type — because it is
   the frame around the offer, not one of the choices. The `/` marks are the
   same glyph and the same --bw-pink as .bw-plan__mark, which is what ties it
   to the grid below. */
.bw-assure {
  margin: 0 0 32px;
  padding: 26px 30px;
  background: var(--bw-panel);
  border: 1.5px solid var(--bw-line);
}

.bw-assure__title {
  font-family: var(--bw-font-mono);
  font-weight: 700;
  font-size: var(--bw-fs-mono-3);
  color: var(--bw-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1.4;
  margin: 0 0 18px;
}

.bw-assure__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px 40px;
}

.bw-assure__item {
  position: relative;
  padding-left: 18px;
  font-size: var(--bw-fs-item);
  color: var(--bw-body);
  line-height: var(--bw-lh-snug);
}

.bw-assure__mark {
  position: absolute;
  left: 0;
  font-family: var(--bw-font-mono);
  color: var(--bw-pink);
}

/* The 2px grid gutters over a cream background read as hairline borders. */
.bw-pricing__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px;
  align-items: stretch;
  border: var(--bw-rule) solid var(--bw-cream);
  background: var(--bw-cream);
}

.bw-plan {
  /* default = the dark variant */
  --plan-bg: var(--bw-panel);
  --plan-fg: var(--bw-cream);
  --plan-body: var(--bw-body);
  --plan-muted: var(--bw-muted);
  --plan-mark: var(--bw-pink);
  --plan-cta-bg: transparent;
  --plan-cta-fg: var(--bw-cream);

  background: var(--plan-bg);
  color: var(--plan-fg);
  padding: 38px 32px;
  display: flex;
  flex-direction: column;
  position: relative;
}

.bw-plan--dark {
  --plan-bg: var(--bw-panel);
  --plan-fg: var(--bw-cream);
  --plan-body: var(--bw-body);
  --plan-muted: var(--bw-muted);
  --plan-mark: var(--bw-pink);
  --plan-cta-bg: transparent;
  --plan-cta-fg: var(--bw-cream);
}

.bw-plan--accent {
  --plan-bg: var(--bw-pink);
  --plan-fg: var(--bw-ink);
  --plan-body: var(--bw-pink-body);
  /* Not --bw-pink-body: pointing muted at the body token cleared AA but
     flattened the /04 kicker, the "/ MĚSÍC" line and the five bullets into
     one ink. See --bw-pink-muted-safe for the numbers behind the value. */
  --plan-muted: var(--bw-pink-muted-safe);
  --plan-mark: var(--bw-ink);
  --plan-cta-bg: var(--bw-ink);
  --plan-cta-fg: var(--bw-cream);
}

.bw-plan__badge {
  position: absolute;
  top: 0;
  right: 0;
  background: var(--bw-ink);
  color: var(--bw-pink);
  font-family: var(--bw-font-mono);
  font-weight: 700;
  font-size: var(--bw-fs-mono-4);
  padding: 8px 14px;
}

.bw-plan__kicker {
  display: block;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-2);
  color: var(--plan-muted);
  margin-bottom: 10px;
}

/* Bottom-aligned inside the 55px reservation, so the name-to-price gap is the
   same 24px on all six cards instead of 38px on the five one-liners and
   10.5px (with a 4.5px overflow) on the two-line one. */
.bw-plan__name {
  display: flex;
  align-items: flex-end;
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-plan);
  line-height: var(--bw-lh-tight);
  letter-spacing: var(--bw-track-display-sm);
  margin: 0 0 24px;
  text-transform: uppercase;
  min-height: 55px;
}

.bw-plan__price-row { margin: 0 0 6px; }

/* No `white-space: nowrap` (the source has it): the &nbsp; inside each amount
   already keeps the thousands groups together, and nowrap also blocked the
   break at the en-dash — which forced 32px of horizontal overflow at the
   320px reflow width under the WCAG 1.4.12 text-spacing overrides.
   Removing it alone was not enough, though: see the 761–845px rule in §20,
   where three columns are too narrow to hold the two range prices at 28px and
   the size has to ramp with the column instead. */
.bw-plan__price {
  font-family: var(--bw-font-display);
  font-size: var(--bw-fs-price);
  line-height: 1;
  letter-spacing: var(--bw-track-display-sm);
}

.bw-plan__per {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-3);
  color: var(--plan-muted);
  margin: 0 0 26px;
}

/* `flex: 0 1 auto` + `margin-top: auto` on the CTA moves the cards' variable
   slack (24px … 91px) out of the middle of the card and into one predictable
   place above the button, so the six read as one system. */
.bw-plan__items {
  margin: 0 0 24px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  flex: 0 1 auto;
}

.bw-plan__item {
  font-size: var(--bw-fs-item);
  color: var(--plan-body);
  line-height: var(--bw-lh-snug);
  padding-left: 16px;
  position: relative;
}

.bw-plan__mark {
  position: absolute;
  left: 0;
  font-family: var(--bw-font-mono);
  color: var(--plan-mark);
}

.bw-plan__note {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-fine);
  color: var(--plan-muted);
  line-height: 1.5;
  margin: 0 0 20px;
  text-transform: uppercase;
}

.bw-plan__cta {
  margin-top: auto;
  background: var(--plan-cta-bg);
  color: var(--plan-cta-fg);
  border: 1.5px solid var(--plan-fg);
  padding: 14px 22px;
}

/* Pink outline on the pink plan would be invisible. */
.bw-plan--accent .bw-plan__cta:focus-visible { outline-color: var(--bw-ink); }

.bw-pricing__fine {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-4);
  color: var(--bw-faint-safe);
  margin: 18px 0 0;
}

/* =========================================================================
   14. FAQ
   ========================================================================= */

.bw-faq {
  border-top: var(--bw-hair) solid var(--bw-line);
  padding: var(--bw-sec-y) var(--bw-gutter);
}

.bw-faq__inner {
  max-width: var(--bw-max-narrow);
  margin: 0 auto;
}

.bw-faq__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-display);
  line-height: var(--bw-lh-display);
  letter-spacing: var(--bw-track-display);
  margin: 0 0 48px;
  text-transform: uppercase;
}

.bw-faq__accent { color: var(--bw-pink); }

.bw-faq__item { border-top: var(--bw-hair) solid var(--bw-line); }

.bw-faq__q {
  font-size: var(--bw-fs-lead);
  font-weight: 700;
  line-height: 1.35;
  padding: 24px 0;
  cursor: pointer;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  min-height: 44px;
}

.bw-faq__q-text {
  min-width: 0;
  transition: color var(--bw-t-hover) ease-out;
}

.bw-faq__q::-webkit-details-marker { display: none; }
.bw-faq__q::marker { content: ""; }

/* Nothing clips this ring, so it takes the default outward offset. */

/* Apple rule 1: feedback on pointer-down, not only on release. These seven
   rows are the most-clicked control on the page. */
.bw-faq__q:active {
  transform: scale(0.995);
  transform-origin: left center;
}

/* Rotation and hover scale are separate custom properties so they compose:
   main.js drives --bw-plus-rot during the animated open/close (it has to run
   ahead of the `open` attribute), CSS drives --bw-plus-scale on hover. */
.bw-faq__plus {
  color: var(--bw-pink);
  font-family: var(--bw-font-mono);
  font-size: 22px;
  line-height: 1;
  flex-shrink: 0;
  transform: rotate(var(--bw-plus-rot, 0deg)) scale(var(--bw-plus-scale, 1));
  transition: transform 220ms var(--bw-ease);
}

.bw-faq__item[open] .bw-faq__plus { --bw-plus-rot: 45deg; }

.bw-faq__a {
  font-size: var(--bw-fs-body);
  color: var(--bw-body);
  line-height: var(--bw-lh-loose);
  padding: 0 0 26px;
  max-width: 700px;
}

.bw-faq__rule { border-top: var(--bw-hair) solid var(--bw-line); }

/* =========================================================================
   15. O nas
   ========================================================================= */

.bw-about {
  border-top: var(--bw-hair) solid var(--bw-line);
  padding: var(--bw-sec-y) var(--bw-gutter);
}

.bw-about__inner {
  max-width: var(--bw-max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: var(--bw-col-gap);
  /* `center` dropped this headline 99px below the section's padding edge —
     the only h2 on the page not sitting on the shared headline line, and
     106px out of step with the photo frame it is paired with. */
  align-items: start;
}

.bw-about__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-display-sm);
  line-height: var(--bw-lh-display-multi);   /* two lines, Czech diacritics */
  letter-spacing: var(--bw-track-display);
  margin: 0 0 28px;
  text-transform: uppercase;
}

.bw-about__text {
  font-size: var(--bw-fs-lead-2);
  color: var(--bw-body);
  line-height: var(--bw-lh-loose);
  margin: 0 0 16px;
}

.bw-about__text:last-of-type { margin-bottom: 40px; }

.bw-about__stats {
  display: flex;
  gap: 48px;
  flex-wrap: wrap;
}

/* At 0.95 the Anton box ended on the exact pixel row the mono label started:
   a 0px figure-to-label gap. The source declares no leading here, so the
   font's own box used to supply the separation; 8px states it explicitly. */
.bw-about__stat-value {
  font-family: var(--bw-font-display);
  font-size: 40px;
  line-height: 1;
  letter-spacing: var(--bw-track-display);
  margin-bottom: 8px;
  color: var(--bw-pink);
}

.bw-about__stat-label {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-2);
  color: var(--bw-muted);
  text-transform: uppercase;
}

.bw-about__media { position: relative; }

.bw-about__frame {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border: var(--bw-rule) solid var(--bw-cream);
  transform: rotate(1.5deg);
}

/* =========================================================================
   16. Kontakt
   ========================================================================= */

.bw-contact {
  border-top: var(--bw-hair) solid var(--bw-line);
  padding: var(--bw-sec-y-lg) var(--bw-gutter);
}

.bw-contact__inner {
  max-width: var(--bw-max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--bw-col-gap);
  align-items: start;
}

/* The markup carries a hard <br>, so this heading is ALWAYS two lines and the
   single-line token never applied: at every width the caron of Ř in TVOŘIT
   fused into the Ď of POJĎME. */
.bw-contact__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-display-xl);
  line-height: var(--bw-lh-display-multi);
  letter-spacing: var(--bw-track-display);
  text-transform: uppercase;
}

.bw-contact__arrow { color: var(--bw-pink); }

.bw-contact__lead {
  font-size: var(--bw-fs-lead-2);
  color: var(--bw-body);
  margin: 28px 0 0;
  max-width: 420px;
  line-height: var(--bw-lh-loose);
}

.bw-contact__alt {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-body-2);
  margin: 20px 0 0;
  color: var(--bw-muted);
}

/* The second address is for people who are already clients, so it is set one
   step quieter than the first: a prospect reading top to bottom should hit
   the enquiry address first and never have to decide which one is theirs. */
.bw-contact__alt--sub {
  margin-top: 8px;
  font-size: var(--bw-fs-mono-4);
  color: var(--bw-faint-safe);
  max-width: 54ch;
}

/* 0.35em clears the tail of Space Mono's `@`, which a 3px offset ran through.
   The padding/negative-margin pair lifts the hit box to 44px without moving
   the baseline — the same trick .bw-nav__link and .bw-footer__link use. */
.bw-contact__mail {
  display: inline-block;
  padding: 11px 2px;
  margin: -11px -2px;
  text-decoration: underline;
  text-underline-offset: 0.35em;
  color: var(--bw-pink);
  transition: color var(--bw-t-hover) ease-out;
}

/* ---- Form ---- */

.bw-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.bw-field { display: flex; flex-direction: column; }

.bw-field--select { position: relative; }

/* appearance:none removes the native caret — draw one back. */
.bw-field--select::after {
  content: "";
  position: absolute;
  right: 20px;
  top: 50%;
  width: 0;
  height: 0;
  margin-top: -2px;
  border: 5px solid transparent;
  border-top-color: var(--bw-muted);
  pointer-events: none;
}

.bw-field__input,
.bw-field__select,
.bw-field__textarea {
  width: 100%;
  background: var(--bw-panel);
  border: 1.5px solid var(--bw-field-line);
  border-radius: 0;
  color: var(--bw-cream);
  font-family: var(--bw-font-body);
  font-size: var(--bw-fs-body);
  line-height: 1.4;
  padding: 17px 20px;
  outline: none;
  transition: border-color var(--bw-t-hover) ease-out, box-shadow var(--bw-t-hover) ease-out;
}

/* text-overflow on a <select> governs the SELECTED option's own render box.
   Without it "Social media marketing & ideamaking" (285.2px) was sliced flat
   at 212px of field — "Social media marketing & id" — with nothing to say a
   word had been cut. */
.bw-field__select {
  color: var(--bw-cream);
  -webkit-appearance: none;
  appearance: none;
  padding-right: 46px;
  min-height: 56px;
  text-overflow: ellipsis;
}

/* Unset reads as a placeholder, exactly like the two inputs above it;
   a real choice reads as filled. #8f887c on the field fill is 5.32:1. */
.bw-field__select:has(option[value=""]:checked) { color: var(--bw-muted); }

.bw-field__select option {
  background: var(--bw-panel);
  color: var(--bw-cream);
}

.bw-field__textarea { resize: vertical; }

.bw-field__input::placeholder,
.bw-field__textarea::placeholder {
  color: var(--bw-muted);
  opacity: 1;
}

.bw-field__input:focus,
.bw-field__select:focus,
.bw-field__textarea:focus { border-color: var(--bw-pink); }

/* A field the browser has REJECTED looked byte-identical to an untouched one:
   same border colour, no shadow, nothing (WCAG 3.3.1 / 1.4.1). :user-invalid,
   not :invalid — the latter matches every empty required field on load and
   would paint the whole form pink before anyone typed. The pink rail is not
   the only signal: .bw-field's ::after states it in words underneath, the same
   place .bw-field__hint speaks from. */
.bw-field__input:user-invalid,
.bw-field__select:user-invalid,
.bw-field__textarea:user-invalid {
  border-color: var(--bw-pink);
  box-shadow: 0 0 0 3px var(--bw-pink-ring);
}

/* :not(--select) because that modifier already owns ::after for the dropdown
   caret, and this rule out-ranks it. The select is never `required`, so the
   two can never both apply today — the guard is so they still cannot if one
   ever becomes required. */
.bw-field:not(.bw-field--select):has(:user-invalid)::after {
  content: "Tenhle údaj ještě není v pořádku, zkontrolujte ho prosím.";
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-4);
  color: var(--bw-pink);
  line-height: 1.5;
  margin: 6px 0 0;
}

/* A real outline plus the glow, not the glow alone: forced-colors strips
   box-shadow, and `outline: none` left nothing behind it. */
.bw-field__input:focus-visible,
.bw-field__select:focus-visible,
.bw-field__textarea:focus-visible {
  border-color: var(--bw-pink);
  outline: var(--bw-focus);
  outline-offset: var(--bw-focus-offset);
  box-shadow: 0 0 0 3px var(--bw-pink-ring);
}

/* `display: block` so the button fills the form column. The arrow is inline
   content, not a sibling flex item, so when "…DO 24H" wraps it follows the
   last word instead of staying vertically centred beside two lines.
   `.bw-btn` is `inline-block` with no `gap`: the separation before the arrow
   is the markup's own word space (Space Mono, 0.601em), which is what makes
   the button's text content verbatim against the design source. */
.bw-form__submit {
  display: block;
  text-align: center;
  padding: 19px 32px;
  font-size: var(--bw-fs-body);
}

/* The submit ships `disabled` in the markup and main.js is what enables it, so
   a page where the script never arrived must not offer a button that looks
   live. The same treatment covers the 15 seconds a send is in flight, where
   main.js disables it again — and main.js's inline `cursor: progress` still
   wins there, so the two states stay distinguishable. */
.bw-form__submit:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* No break opportunity in front of the arrow: in the two narrow bands where
   the label has to wrap (measured <=378px, and 761-804px where the contact
   grid gives the form its narrowest column) an ordinary space leaves the
   arrow stranded alone on the second line. The space is a real one — the
   source's string is verbatim, arrow included — so the last word and the
   arrow are held together by a nowrap span in the markup, not by a margin. */
.bw-btn__tail { white-space: nowrap; }

.bw-form__fine {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-4);
  color: var(--bw-faint-safe);
  line-height: 1.5;
}

.bw-form__status {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-4);
  color: var(--bw-body);
  line-height: 1.5;
  margin: 0;
}

/* NOT display:none — that removes the node from the accessibility tree, so the
   region and its first message would materialise in the same task and screen
   readers would announce nothing. Empty and weightless, but present. */
.bw-form__status:empty {
  padding: 0;
  border: 0;
  margin: 0;
  min-height: 0;
  font-size: 0;
}

/* Success and failure have to differ by more than wording: the line is short,
   it appears under a long button, and role="status" only reaches AT. A 3px
   rail on the leading edge does it at a glance, and it is a colour we already
   own — pink for a problem, cream for a clean hand-off — never colour alone,
   since the wording carries the same information.
   Both the BEM modifier and the data-attribute form are matched, so main.js
   can publish the state either way. */
.bw-form__status--ok,
.bw-form__status--error,
.bw-form__status[data-state] {
  border-left: 3px solid var(--bw-line);
  padding: 10px 0 10px 14px;
}

.bw-form__status--ok,
.bw-form__status[data-state="ok"],
.bw-form__status[data-state="success"] {
  border-left-color: var(--bw-cream);
  color: var(--bw-cream);
}

.bw-form__status--error,
.bw-form__status[data-state="error"] {
  border-left-color: var(--bw-pink);
  color: var(--bw-cream);
}

/* ---- Echo line ----------------------------------------------------------
   Rendered by main.js above the fields when a visitor arrives from a POPTAT
   button, to confirm which plan was carried over. Same rail language as the
   status line, pink because it is the one line on the form that came from the
   visitor's own click. Hidden while empty so the form's 16px gap does not
   open a hole before the script has anything to say. */
.bw-form__echo,
[data-bw-echo] {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-2);
  line-height: 1.5;
  color: var(--bw-body);
  background: var(--bw-panel);
  border-left: 3px solid var(--bw-pink);
  padding: 12px 16px;
  margin: 0;
}

/* NOT display:none, for the same reason .bw-form__status:empty is not: the
   container is now shipped in the markup precisely so the live region exists
   in the accessibility tree before it has anything to say, and display:none
   would take it straight back out. Parked off-canvas instead — no ground, no
   rail, and no flex gap opened in the form. */
.bw-form__echo:empty,
[data-bw-echo]:empty {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
}

.bw-form__echo strong,
.bw-form__echo b,
.bw-form__echo-value {
  color: var(--bw-cream);
  font-weight: 700;
}

/* main.js builds the "ZMĚNIT" control as `.bw-btn .bw-contact__mail`, which
   is a sound borrow but drags `.bw-btn`'s `min-height: 44px` and 14px mono
   into a 13px text line — the echo grew to 46px and the label floated off the
   baseline. Drop the min-height and buy the same 44px target back the way
   .bw-nav__link does, with padding against an equal negative margin, so the
   tap area is honoured and the line box is not. */
.bw-form__echo-change {
  min-height: 0;
  font-size: inherit;
  line-height: inherit;
  padding: 13px 4px;
  margin: -13px 0 -13px -4px;
  vertical-align: baseline;
}

/* The inline mail link main.js drops into the status line and the done panel.
   `.bw-contact__mail` already gives it pink, an underline and a 44px target;
   all it needs is to stop inheriting that rule's own 15px and sit on the
   surrounding text's size. */
.bw-form__status-mail { font-size: inherit; }

/* ---- Success panel ------------------------------------------------------
   Replaces the whole form once the poptávka is away, so it has to look like
   a resolved state and not like a form that lost its fields. Same panel +
   pink rail as the echo and the status line — one visual family for
   "the script is talking to you" — with the display heading pulled down to
   --bw-fs-price: it borrows .bw-service__title, whose clamp reaches 52px and
   would out-shout the section's own h2 from inside a form column. */
.bw-form--done {
  gap: 14px;
  padding: 34px 32px;
  background: var(--bw-panel);
  border: 1.5px solid var(--bw-line);
  border-left: 3px solid var(--bw-pink);
}

.bw-form__done-kicker {
  color: var(--bw-pink);
  letter-spacing: 0.08em;
  margin: 0;
}

.bw-form__done-title {
  font-size: var(--bw-fs-price);
  letter-spacing: var(--bw-track-display-sm);
  margin: 0;
}

.bw-form__done-text {
  font-size: var(--bw-fs-lead-3);
  max-width: 46ch;
  margin: 0;
}

/* The visitor's own address, quoted back. Cream lifts it out of the sentence
   without a second colour entering the panel. */
.bw-form__done-mail {
  color: var(--bw-cream);
  font-weight: 700;
}

/* ---- "needs JavaScript" note -------------------------------------------
   Shipped visible and taken down by main.js. It has to cover two states, not
   one: scripting switched off, AND scripting on with main.js never arriving
   (404, blocked, parse error) — the second is invisible to <noscript> and was
   the state in which the submit button posted the lead into the unfilled
   endpoint and lost it. Same treatment as the old noscript block: pink rule,
   panel ground, body-size type, because it is the one thing standing between
   that visitor and a dead form. */
.bw-form__nojs {
  border: 1.5px solid var(--bw-pink);
  background: var(--bw-panel);
  color: var(--bw-cream);
  font-size: var(--bw-fs-body-2);
  line-height: var(--bw-lh-body);
  padding: 18px 20px;
  margin: 0;
}

.bw-form__nojs-mail {
  color: var(--bw-pink);
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

/* main.js adds .bw-form--live on its first pass, after it has enabled the
   submit button. Keyed on the form, not on the button's own disabled state,
   so the note does not come back during the 15 seconds a submit is in flight
   (busy() disables the button too). */
.bw-form--live .bw-form__nojs { display: none; }

/* ---- Optional-field hint ------------------------------------------------
   Sits under the phone input inside .bw-field (flex column), so the 6px is a
   real gap to the field above it, not a collapsing margin. Mono at fine-print
   size keeps it subordinate to the placeholder it explains; --bw-faint-safe
   rather than --bw-faint because this sits on --bw-bg (5.53:1 vs 3.32:1). */
.bw-field__hint {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-4);
  color: var(--bw-faint-safe);
  line-height: 1.5;
  margin: 6px 0 0;
}

/* The GDPR line is --bw-faint-safe; a link inside it has to lift out of that
   without becoming pink-on-near-black fine print. Cream + underline does it,
   and the generic a:hover still takes it to pink. */
.bw-form__fine-link {
  color: var(--bw-cream);
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

/* =========================================================================
   17. Sticky mobile CTA  (main.js toggles display/opacity/transform inline)
   ========================================================================= */

.bw-sticky {
  position: fixed;
  /* Landscape moves the notch to a side, so the horizontal pair needs the inset
     as much as the bottom one does. env() is 0 everywhere else, so 16px stands. */
  left: max(16px, env(safe-area-inset-left, 0px));
  right: max(16px, env(safe-area-inset-right, 0px));
  /* On notched iPhones a flat 16px puts the button inside the home-indicator
     gesture strip. env() resolves to 0 elsewhere, so 16px is kept. */
  bottom: max(16px, env(safe-area-inset-bottom, 16px));
  z-index: 60;
  display: none;
  opacity: 0;
  transform: translateY(80px);
  pointer-events: none;
  /* Solid dark plate under the button. Without it the bar was a bare pink
     rectangle: over --bw-plan--accent (the /04 card, also --bw-pink) it
     vanished into the card entirely, and over every other surface its edges
     read as part of the page rather than as a floating control. The plate
     gives it an outline everywhere and lifts it off whatever scrolls beneath.
     The button keeps its own glow, which the notch clip-path eats — the plate
     carries the real elevation now. */
  background: var(--bw-bg);
  border: var(--bw-hair) solid var(--bw-line);
  padding: 10px;
  box-shadow: 0 10px 34px rgb(var(--bw-bg-rgb) / 0.75);
  transition: opacity 300ms ease-out, transform 400ms var(--bw-ease);
}

.bw-sticky__btn {
  display: block;
  width: 100%;
  padding: 18px;
  font-size: var(--bw-fs-body);
  box-shadow: 0 8px 30px var(--bw-pink-glow);
}

/* =========================================================================
   18. Footer
   ========================================================================= */

/* The footer is no longer `brand + nav` on one line. It now stacks three
   blocks — the pre-launch banner, the old brand/social row, and the § 435
   identity block — so the flex axis turns vertical here and .bw-footer__row
   inherits the row behaviour the container used to own. */
.bw-footer {
  border-top: var(--bw-hair) solid var(--bw-line);
  padding: 26px var(--bw-gutter) 34px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-2);
}

.bw-footer__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
}

.bw-footer__brand { color: var(--bw-muted); }

.bw-footer__star { color: var(--bw-pink); }

.bw-footer__social {
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
}

.bw-footer__social-item { display: flex; }

.bw-footer__link {
  display: inline-block;
  color: var(--bw-muted);
  padding: 12px 4px;          /* a11y: 44px tall hit area, layout unchanged */
  margin: -12px -4px;
  transition: color var(--bw-t-hover) ease-out;
}

/* ---- Identity block -----------------------------------------------------
   Hairline above it, fine-print size. It held the § 435 trader identification
   until there was a trade licence to state; see the restore note in the
   footer markup. What is left is the contact address and the legal links, so
   the 78ch measure below now only has to hold one short line -- it stays
   because the identification comes back into this block, not next to it. */
.bw-footer__identity {
  border-top: var(--bw-hair) solid var(--bw-line);
  padding-top: 22px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Unused while the trader identification is off the page. Kept, not deleted:
   the markup that carries this class is restored verbatim from the comment in
   the footer, and it should land already styled. */
.bw-footer__legal {
  font-size: var(--bw-fs-mono-4);
  color: var(--bw-faint-safe);
  line-height: 1.7;
  max-width: 78ch;
}

.bw-footer__contact {
  font-size: var(--bw-fs-mono-4);
  color: var(--bw-faint-safe);
}

.bw-footer__mail {
  color: var(--bw-cream);
  text-decoration: underline;
  text-underline-offset: 0.3em;
  transition: color var(--bw-t-hover) ease-out;
}

.bw-footer__legal-list {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
}

.bw-footer__legal-item { display: flex; }

/* ---- Pre-launch banner --------------------------------------------------
   Deliberately the loudest thing in the footer. It is not decoration: while
   it is on the page the site is not ready to publish (dead social links, no
   sharing image, dead form endpoint, and on the privacy page an unnamed data
   controller, which check-chrome.py fails on). Pink ground, ink text, and
   it carries the grep command that clears it. The owner deletes this block
   from both HTML files as the last step. */
.bw-footer__todo {
  background: var(--bw-pink);
  color: var(--bw-ink);
  padding: 20px 24px;
}

.bw-footer__todo-title {
  font-family: var(--bw-font-mono);
  font-weight: 700;
  font-size: var(--bw-fs-mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1.35;
  margin: 0 0 10px;
}

.bw-footer__todo-text {
  font-family: var(--bw-font-body);
  font-size: var(--bw-fs-body-2);
  color: var(--bw-pink-body);
  line-height: var(--bw-lh-body);
  max-width: 92ch;
  margin: 0;
}

/* =========================================================================
   18b. Legal page  (ochrana-osobnich-udaju.html)
   -------------------------------------------------------------------------
   Same stylesheet, same tokens, different job: this page is read top to
   bottom, so it gets one column at a real measure instead of the landing
   page's composed blocks. The container is capped by whichever is smaller —
   --bw-max-narrow or 68 characters of its own 17px body — so the line never
   runs past a comfortable measure on a wide screen and never gets artificially
   narrowed on a small one. Rhythm comes from a hairline above every <h2>,
   which is the page's own language (.bw-service, .bw-faq__item, .bw-pricing).
   ========================================================================= */

.bw-legal { padding: 72px var(--bw-gutter) var(--bw-sec-y-lg); }

/* 57ch, not 65: `ch` is the advance of "0", and Space Grotesk's zero is 16%
   wider than its average letter, so `ch` overstates the measure. Counted on
   the rendered lines with a Range probe — 68ch gave a 77-character median and
   a 90-character worst line; 57ch lands the median at 65 and the longest line
   under 76. --bw-max-narrow stays as the hard ceiling for the day the base
   size changes. */
.bw-legal__inner {
  margin: 0 auto;
  font-size: var(--bw-fs-lead-3);
  max-width: min(var(--bw-max-narrow), 57ch);
}

/* No border-bottom here: .bw-legal__section already opens with a border-top,
   and the two drew a pair of hairlines 37px apart under the intro. */
.bw-legal__head { margin-bottom: 0; }

.bw-legal__kicker {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-3);
  color: var(--bw-pink);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0 0 18px;
}

.bw-legal__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-display-sm);
  line-height: var(--bw-lh-display-ring);    /* two lines, and Ů needs the ring value */
  letter-spacing: var(--bw-track-display);
  text-transform: uppercase;
  margin: 0 0 24px;
}

.bw-legal__lead {
  font-size: var(--bw-fs-lead-2);
  color: var(--bw-body);
  line-height: var(--bw-lh-loose);
  margin: 0 0 20px;
}

.bw-legal__updated {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-4);
  color: var(--bw-faint-safe);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0;
}

.bw-legal__section {
  border-top: var(--bw-hair) solid var(--bw-line);
  padding-top: 38px;
  margin-top: 38px;
}

.bw-legal__h2 {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-plan);
  line-height: var(--bw-lh-tight);
  letter-spacing: var(--bw-track-display-sm);
  text-transform: uppercase;
  margin: 0 0 20px;
}

.bw-legal__text {
  color: var(--bw-body);
  line-height: var(--bw-lh-loose);
  margin: 0 0 16px;
}

.bw-legal__text:last-child { margin-bottom: 0; }

.bw-legal__text strong,
.bw-legal__item strong { color: var(--bw-cream); font-weight: 700; }

.bw-legal__text--address {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-body-2);
  line-height: 1.9;
  border-left: var(--bw-rule) solid var(--bw-line);
  padding-left: 20px;
}

.bw-legal__list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 0 0 16px;
}

.bw-legal__item {
  position: relative;
  padding-left: 20px;
  color: var(--bw-body);
  line-height: var(--bw-lh-loose);
}

.bw-legal__mark {
  position: absolute;
  left: 0;
  font-family: var(--bw-font-mono);
  color: var(--bw-pink);
}

/* 0.3em clears the descender of the `j` in "údajů" and the tail of Space
   Grotesk's `@` in the two mailto links. */
.bw-legal__link {
  color: var(--bw-pink);
  text-decoration: underline;
  text-underline-offset: 0.3em;
  transition: color var(--bw-t-hover) ease-out;
}

.bw-legal__back {
  border-top: var(--bw-hair) solid var(--bw-line);
  padding-top: 34px;
  margin-top: 44px;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* The one way back off this page, and it was a 203x20px inline box in a
   paragraph of its own — no WCAG 2.5.8 inline exception applies there. Same
   padding-against-negative-margin trick as .bw-nav__link: 44px of target, and
   the baseline does not move. */
.bw-legal__back .bw-legal__link {
  display: inline-block;
  padding: 12px 4px;
  margin: -12px -4px;
  text-decoration: none;
}

/* =========================================================================
   18c. Launch blockers
   -------------------------------------------------------------------------
   Everything the owner still has to fill in is marked here, loudly and in
   three independent ways, so an unfilled placeholder cannot ship quietly:

     .bw-todo         the token itself, wherever it is printed as text
     [href^="@@"]     a link whose target is still a placeholder. This one is
                      keyed on the VALUE, not on a class or a data attribute,
                      so it clears itself the moment a real URL is pasted in —
                      there is no second edit to forget.
     .bw-footer__todo the banner above (§ 18)

   The attribute selectors match the token's opening "@@" only, deliberately:
   this file must not itself contain the full token, or the owner's documented
   pre-launch check would report a hit here forever and he would learn to
   ignore it. Those two clear themselves the moment a real value is pasted in.

   .bw-todo cannot: it is a CLASS on a wrapper <span>, so replacing the token
   inside it leaves the pink chip painting the owner's real name, sídlo and IČO
   across the footer. main.js strips the class in the same pass that removes
   the banner, which is what makes the claim "nothing here survives launch"
   true rather than merely intended.
   ========================================================================= */

.bw-todo {
  font-family: var(--bw-font-mono);
  font-weight: 700;
  font-size: 0.94em;
  background: var(--bw-pink);
  color: var(--bw-ink);
  padding: 0.1em 0.4em;
  /* No nowrap: the grep command printed in the banner and the longer tokens
     are wider than a 320px content box and would force horizontal scroll. */
  overflow-wrap: anywhere;
}

/* Inverted inside the pink banner, where a pink chip would disappear. */
.bw-footer__todo .bw-todo {
  background: var(--bw-ink);
  color: var(--bw-pink);
}

[href^="@@"],
[src^="@@"],
[content^="@@"] {
  color: var(--bw-pink);
  text-decoration: underline;
  text-decoration-style: wavy;
  text-underline-offset: 0.3em;
  outline: 1.5px dashed var(--bw-pink);
  outline-offset: -3px;
}

/* =========================================================================
   19. Hover states (pointer devices only — no sticky hover on touch)
   ========================================================================= */

@media (hover: hover) and (pointer: fine) {
  /* `:not(.bw-btn)` is load-bearing, not tidiness. `a:hover` is (0,1,1) and
     `.bw-btn--pink` is only (0,1,0), so the generic link hover was repainting
     every pink button's LABEL pink — on a pink fill. It never showed because
     `.bw-btn--pink:hover` used to swap the background to cream and restate the
     colour at (0,2,0). The moment that swap went (see below) the hero CTA
     hovered to a blank pink slab: no text at all, verified in a screenshot.
     Buttons carry their own hover states; the generic rule is for links. */
  a:not(.bw-btn):hover { color: var(--bw-pink); }

  .bw-nav__link:hover,
  .bw-footer__link:hover { color: var(--bw-pink); }

  /* The primary CTA used to turn CREAM on hover — the exact fill the
     secondary already carries as its text colour — so at the moment of
     highest intent "CHCI NABÍDKU" and "FORMÁTY A UKÁZKY" became the same
     object and the hierarchy the whole page is built on collapsed. The
     primary now stays pink and answers with elevation instead: a 2px lift, a
     pink glow, and a fractional brightening.

     The glow is there for the pink buttons that are not notched. Every
     notched one is carried by the lift and the brightening instead: measured,
     --bw-notch-lg clips the outer shadow away completely, and a
     `filter: drop-shadow()` too — clip-path is applied after filter, so the
     glow never reaches the canvas either way. Both painted inside the clip.
     Same reason .bw-sticky now has its own plate.

     :active is redeclared here because it has to out-order this rule — the
     base `.bw-btn:active` sits 1200 lines earlier at equal specificity, so
     without this the pointer-down scale never appeared while hovering. */
  .bw-btn--pink:hover {
    color: var(--bw-ink);
    transform: translateY(-3px);
    box-shadow: 0 12px 28px var(--bw-pink-glow);
    filter: brightness(1.08);
  }

  .bw-btn--pink:active {
    color: var(--bw-ink);
    transform: translateY(-1px) scale(0.97);
    box-shadow: 0 4px 14px var(--bw-pink-glow);
    filter: brightness(1.08);
  }

  .bw-btn--outline:hover {
    border-color: var(--bw-pink);
    color: var(--bw-pink);
  }

  .bw-footer__mail:hover,
  .bw-form__fine-link:hover { color: var(--bw-pink); }

  /* Already pink at rest, so the generic a:hover would be a no-op — invert. */
  .bw-form__nojs-mail:hover,
  .bw-legal__link:hover { color: var(--bw-cream); }

  /* The source's `opacity: .8` fade dropped this label from 15.5:1 to 3.67:1
     AND the generic a:hover leaked pink through it (0,1,1 beats 0,1,0), so
     the one hover on the page that reduced legibility. Invert instead — the
     same language .bw-btn--pink already speaks — which stays above 15:1 and
     raises specificity enough to kill the leak. */
  .bw-plan__cta:hover {
    background: var(--plan-fg);
    color: var(--plan-bg);
    opacity: 1;
  }

  .bw-service:hover { background: var(--bw-panel); }

  /* Without this, hover meant "not clickable" and no-hover meant "clickable". */
  .bw-faq__q:hover .bw-faq__q-text { color: var(--bw-pink); }
  .bw-faq__q:hover .bw-faq__plus { --bw-plus-scale: 1.15; }

  .bw-contact__mail:hover { color: var(--bw-cream); }

  /* Same exclusion, and here it matters more: this rule is (0,2,1) and would
     out-rank even `.bw-btn--pink:hover`, so the CHCI UKÁZKY button inside the
     cream section would have hovered to a blank pink slab too. */
  .bw-works a:not(.bw-btn):hover { color: var(--bw-pink); }
}

/* =========================================================================
   20. Intermediate breakpoint — 761px … 1100px
   ========================================================================= */

@media (min-width: 761px) and (max-width: 1100px) {
  .bw-about__inner,
  .bw-contact__inner { gap: 48px; }

  .bw-service { gap: 20px; }
}

/* The nav carries six items now, not five, and one of them is PERFORMANCE —
   eleven characters of Space Mono. Measured: the row costs logo 124 + gap 20 +
   (SLUŽBY 50 + PERFORMANCE 92 + PRÁCE 42 + CENÍK 43 + O NÁS 42 + KONTAKT 59 +
   6 x 30 of gap + CTA 151) = 803px, so it stopped fitting at 861px and
   `flex-wrap: wrap` grew the header from 80px to 119px all the way down to the
   761px breakpoint where --hide-sm takes over. Tightened instead of hiding an
   item: at 761 the same row costs 124 + 12 + (328 + 6 x 16 + 135) = 695px
   against 681px of content box... which is still 14px short, so the gutter
   comes in too. Nothing is dropped and nothing overflows.

   The upper bound is 1000px, not 1100px: at 1001px there is 132px of slack,
   so the step is invisible unless you drag the window across it on purpose.

   RE-MEASURED for the CS/EN switch (section 35). The row gained an eighth
   item — 38.4px of control plus a seventh gap — and lost 51.5px when the nav
   item PERFORMANCE (94.3px) became VÝKON (42.8px). Net it was still 3px over
   at 761px: measured 0.7px of slack, which is a rounding error, not a margin.
   The menu gap goes 16px -> 13px, which buys back 7 x 3 = 21px and puts 21.7px
   of measured slack at 761. At 1000px there are 239px of slack either way, so
   the three pixels are invisible at the top of the band and load-bearing at
   the bottom of it. */
@media (min-width: 761px) and (max-width: 1000px) {
  .bw-nav {
    gap: 12px;
    padding-left: 28px;
    padding-right: 28px;
  }

  .bw-nav__menu { gap: 13px; }

  .bw-nav__cta {
    white-space: nowrap;
    padding-left: 16px;
    padding-right: 16px;
  }
}

/* The process grid is `repeat(auto-fit, minmax(230px, 1fr))` — the source's
   own rule, and correct at both ends. It only misbehaves in the middle:
   from ~745px to ~999px auto-fit yields THREE tracks for FOUR steps (a 3+1
   orphan), and from 1000px to ~1149px it yields four tracks narrow enough
   that only step 04's title wraps, dropping its body copy 22px below its
   siblings. Measured; aligned again from 1150px up. A clean 2x2 covers the
   whole misbehaving band. */
@media (min-width: 690px) and (max-width: 1149px) {
  .bw-process__list { grid-template-columns: repeat(2, 1fr); }
  /* The first item of each row is the one that sits on the grid line. */
  .bw-step:nth-child(2n + 1) {
    border-left: 0;
    padding-left: 0;
  }
}

/* The one band where the three-column grid is narrower than its content.
   Each card is one third of `layout - 88px` (40px gutters, the 2px cream rule
   and the two 2px cream gaps), so at the source's 32px side padding the widest
   price, "10 000–20 000 Kč" at 184.67px in 28px Anton, needs a LAYOUT width of
   834px. Below that it broke over two lines; the source sets it on one line.

   Fixed by size, not by `white-space: nowrap` — nowrap is what overflowed the
   320px reflow width under the WCAG 1.4.12 text-spacing overrides. Trimming
   the side padding first (32 → 24px, where the card is at its narrowest
   anyway) buys 16px of content box, so the price only has to ramp from 24px
   to 28px instead of from 22px.

   The upper bound is a WINDOW width, but the 834px above is a LAYOUT width,
   and media queries are evaluated against the window — scrollbar included.
   With a classic 15px scrollbar the two differ, which is exactly why 846px
   was too low: at a 846–848px window the layout is 831–833px, the rule had
   already switched off, and the price wrapped again. Measured: the first
   window that clears 834px of layout is 834 with overlay scrollbars and 849
   with classic ones. 860px is that 849 plus margin — it holds for any
   scrollbar up to 26px wide, and still leaves 3.98px of slack at 861.

   Second move: the price also has to stay bigger than the name it outranks.
   The ramp bottoms out at 24.13px at 761px, under the 25px `--bw-fs-plan`, so
   the name rides the same ramp 3px lower. It reaches its own 25px cap at a
   846.15px window — inside the band — so it hands back to the fixed 25px
   above 860px without a step. */
@media (min-width: 761px) and (max-width: 860px) {
  .bw-plan {
    padding-left: 24px;
    padding-right: 24px;
  }

  .bw-plan__price { font-size: min(28px, calc(4.55vw - 10.5px)); }
  .bw-plan__name { font-size: min(25px, calc(4.55vw - 13.5px)); }
}

/* =========================================================================
   21. Mobile — 760px and below (real media query for the source's
   !important attribute-selector hacks)
   ========================================================================= */

@media (max-width: 760px) {
  /* Craft: the vertical scale has to answer to the viewport. A 390px phone
     was getting the same 100–110px bands as a 1440px desktop — up to 220px,
     26% of an 844px screen, of empty space between every two sections.
     Redeclaring the tokens keeps the desktop composition untouched. */
  :root {
    --bw-sec-y-sm: 60px;
    --bw-sec-y: 68px;
    --bw-sec-y-lg: 76px;
  }

  /* The h1's leading used to be redeclared here at 1.15 because 0.92 collided
     at 64px. It collides at 158px too, so the value moved into --bw-lh-hero
     and this override became a duplicate. */

  .bw-hero {
    padding-top: 60px;
    padding-bottom: 48px;
  }

  .bw-stats { padding-top: 48px; }

  /* [data-bw-pad] */
  .bw-hero,
  .bw-stats,
  .bw-services,
  .bw-works,
  .bw-process,
  .bw-cta--mid,
  .bw-pricing,
  .bw-faq,
  .bw-about,
  .bw-contact,
  .bw-legal,
  .bw-page,
  .bw-section,
  .bw-footer {
    padding-left: var(--bw-gutter-sm);
    padding-right: var(--bw-gutter-sm);
  }

  /* [data-bw-nav] */
  .bw-nav { padding: 14px var(--bw-gutter-sm); }

  /* [data-bw-navlinks] */
  .bw-nav__menu { gap: 18px; }

  /* [data-bw-svc] */
  .bw-service {
    grid-template-columns: 1fr;
    gap: 6px;
    padding: 26px 0;
  }

  /* [data-bw-onas] */
  .bw-about__inner,
  .bw-contact__inner {
    grid-template-columns: 1fr;
    gap: 44px;
  }

  /* Once the grid collapses, the 4:5 frame stretched to the full content
     width — an empty dashed box up to 919px tall, taller than the phone it
     sits in. Cap the column; the ratio and the 1.5deg tilt are untouched. */
  .bw-about__media { max-width: 380px; }

  /* [data-bw-bleed] */
  .bw-works__viewport {
    margin-left: calc(-1 * var(--bw-gutter-sm));
    margin-right: calc(-1 * var(--bw-gutter-sm));
    padding-left: var(--bw-gutter-sm);
    padding-right: var(--bw-gutter-sm);
  }

  /* [data-bw-shift] */
  .bw-work--shift { transform: none; }

  /* [data-bw-plans] */
  .bw-pricing__grid { grid-template-columns: 1fr; }

  /* The sticky CTA is fixed 16px off the bottom and roughly 96px tall with its
     new plate. The last two FAQ rows are the ones a visitor opens after
     scrolling to the end of the section, and their answers were opening
     underneath it — the answer scrolled into the strip and simply could not be
     read. Reserve the bar's full height plus its offset below the section. */
  .bw-faq { padding-bottom: calc(var(--bw-sec-y) + 112px); }

  /* One column: side by side these four lines were 22 characters wide. */
  .bw-assure__list { grid-template-columns: 1fr; }


  .bw-assure { padding: 22px 20px; }

  /* The CTA drops under the copy, so it stops being a second column and the
     `end` alignment would strand it against the right edge. */
  .bw-works__offer {
    grid-template-columns: minmax(0, 1fr);
    padding: 26px 22px;
  }

  .bw-works__offer-cta {
    grid-column: 1;
    grid-row: auto;
    justify-self: stretch;
    text-align: center;
    white-space: normal;
  }

  .bw-cta--faq {
    margin-top: 40px;
    padding: 32px 22px;
  }

  .bw-form--done { padding: 26px 20px; }

  .bw-legal {
    padding-top: 48px;
  }

  .bw-legal__section {
    padding-top: 30px;
    margin-top: 30px;
  }

  .bw-footer__todo { padding: 18px 18px; }
}

/* Narrow-viewport reflow. Measured a pixel at a time: CO PRO VÁS UDĚLÁME
   wraps at 343px and KLIPY, KTERÉ FUNGUJÍ at 341px, and on the second line the
   Ě caron and the Á acute are then drawn into the line above. --bw-lh-display
   is right everywhere they fit on one line; 380px leaves margin for a fallback
   face, and every real phone width (390, 414, 430) is above it and untouched. */
@media (max-width: 380px) {
  .bw-services__title,
  .bw-works__title { line-height: var(--bw-lh-display-multi); }
}

/* [data-bw-hide] — the source's own breakpoint, and BUILD-BRIEF.md's. An
   earlier pass moved it to 719px because 760 "left a lonely CENIK with 400px
   of void beside it"; that void is the design. Verified the row still fits
   one line above the breakpoint: at 761px the logo ends at 164.3px and the
   link group starts at 231.6px — 67.4px of slack, nav height 80.3px (one
   row), with all five links up. */
@media (max-width: 760px) {
  .bw-nav__item--hide-sm { display: none; }
}

/* ---- The phone ladder ---------------------------------------------------
   Below 761px the row is four objects: the wordmark, CENÍK, the CS/EN switch
   (section 35) and the CTA. Everything else is --hide-sm and lives in the
   footer sitemap.

   The budgets below were re-measured twice. First when the nav CTA changed —
   it used to say "CHCI KLIPY" (10 characters, 118px) and now says "CHCI
   NABÍDKU" (12 characters, 151px), and 33px is enough to break the row at
   390px and at 320px. Then again when the language switch landed: it is a
   fourth object worth 38.4px plus a second flex gap, i.e. ~57px on a row that
   had 14px of slack at 400px and 8.1px at 320px. There is no version of this
   where the switch is free, so the ladder below buys the width back in four
   measured steps rather than one clever one.

   Nothing is dropped above 344px. Every number here was read off the live
   page, not computed.

   STEP 1 — 519px and down. The roomy settings hold to about 452px (measured
   9.6px of slack there), so the step is set at 519 to leave the whole phone
   range on one rule and keep 520-760px untouched, where there is 78px+ of
   slack anyway. Gaps tighten and the CTA drops to the small mono size.
   Measured margin: 117.7px at 519, 50.7 at 452, 28.7 at 430 (iPhone 16 Pro
   Max). It runs out at about 402, which is why step 2 starts at 429. */
@media (max-width: 519px) {
  .bw-nav { gap: 12px; }
  .bw-nav__menu { gap: 12px; }

  .bw-nav__cta {
    white-space: nowrap;
    font-size: var(--bw-fs-mono-3);
    padding-left: 14px;
    padding-right: 14px;
  }
}

/* STEP 2 — 429px and down, which is where the common phone widths live: 412
   (Pixel), 393 (iPhone 16), 390 (iPhone 12-15). The wordmark comes down from
   24px to 20px and the gutter from 20px to 16px.
   Measured margin: 64.7px at 429, 47.7 at 412, 35.7 at 400, 25.7 at 390. It
   runs out at about 372, which is why step 3 starts at 379. */
@media (max-width: 429px) {
  .bw-nav {
    padding-left: 16px;
    padding-right: 16px;
    gap: 10px;
  }

  .bw-nav__logo { font-size: 20px; }
  .bw-nav__menu { gap: 10px; }

  .bw-nav__cta {
    padding-left: 12px;
    padding-right: 12px;
  }
}

/* STEP 3 — 379px and down: iPhone SE and iPhone 8 at 375, and the 360px
   Galaxy widths. Everything that is left to tighten, tightens.
   Measured margin: 28.7px at 379, 24.7 at 375, 17.7 at 368, 9.7 at 360. */
@media (max-width: 379px) {
  .bw-nav {
    padding-left: 14px;
    padding-right: 14px;
    gap: 8px;
  }

  .bw-nav__menu { gap: 8px; }

  .bw-nav__cta {
    padding-left: 10px;
    padding-right: 10px;
  }
}

/* STEP 4 — 343px and down, and the only step that drops something. This is
   the band that has to hold at 320px: the WCAG 1.4.10 reflow width, and the
   only width on this list where a broken header is a failed success criterion
   rather than an ugly one.

   At 320px the row cannot carry the wordmark, CENÍK, the switch AND the CTA.
   Measured, maximally tightened, with the switch at 11px it is still ~6px
   over, so one of the three has to go and the choice is between them:

     * the CTA is the business, and the sticky mobile CTA does not cover for
       it — main.js only reveals that one after the hero has scrolled away;
     * the switch is the thing this pass exists to add, and the brief is
       explicit that it has to fit at 320;
     * CENÍK is one tap away in the footer sitemap, which is on every page
       precisely because "on a phone this list is the only complete path to
       the other pages" (see the footer's own comment).

   So CENÍK steps out, and only below 344px. Every phone in the wild keeps it:
   360px (Galaxy S) has 9.7px of slack with CENÍK still up and 375px has 24.7,
   so the breakpoint sits one pixel under the narrowest phone anybody actually
   holds. 320px is reached by a first-generation iPhone SE and by 400% zoom,
   and it keeps the switch, the CTA and 20.5px of measured slack. */
@media (max-width: 359px) {
  .bw-nav__item--hide-xs { display: none; }
}

/* =========================================================================
   21b. Short viewports — sticky chrome must not eat the screen
   -------------------------------------------------------------------------
   Measured scrolled into #cenik: at 320x256 (1280x1024 at 400% zoom, WCAG
   1.4.10's own reference size) the sticky header's 72px plus the fixed mobile
   CTA's 97.2px left 86.8px of 256 — 34% — and the pink CTA was drawn over the
   "KOLIK TO STOJÍ" heading. At 568x320 it was 47%, at 667x375 55%. Below
   480px of viewport height both stand down: the header scrolls with the page
   (relative, not static, so .bw-header__edge keeps its containing block) and
   the CTA hides — every action it duplicates is still in the page flow, and
   the width test already hides it on landscape phones, which are wider than
   760px.
   ========================================================================= */

@media (max-height: 480px) {
  html { scroll-padding-top: 16px; }

  .bw-header { position: relative; }

  /* main.js writes `display: block` inline on this element, so the stand-down
     has to out-rank an inline style. */
  .bw-sticky { display: none !important; }
}

/* =========================================================================
   22. Preferences
   ========================================================================= */

/* Apple rule 14: cross-fade instead of springs and slides. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }

  /* The six plans are staggered by `transition-delay`, not by duration, and
     they are children of the [data-reveal] grid rather than reveals of their
     own — so neither the duration reset above nor the [data-reveal] rule
     reached them, and they still popped in sequence over 275ms. */
  [data-reveal],
  [data-reveal].bw-in,
  .bw-pricing__grid > .bw-plan,
  .bw-pricing__grid.bw-in > .bw-plan {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0s;
  }

  .bw-marquee__track {
    animation: none;
    transform: none;
    will-change: auto;
  }

  .bw-hero__title,
  .bw-hero__foot { animation: none; }

  /* The sign-off, killed the way Voidex kills its own: `animation: none` and
     nothing else, because nothing else is needed — the word's resting style
     is already the finished state, so removing the animation leaves it
     exactly where the rise would have put it, full size, full opacity, still.

     `!important` is load-bearing here, and it is the only reason this rule
     works. Section 25 declares the same two selectors at the same
     specificity and lives 300-odd lines FURTHER DOWN the file, so on source
     order alone it wins and this rule is dead. Measured before the
     `!important` went in: under prefers-reduced-motion the computed
     animation-name was still `bw-signoff-rise` / `bw-signoff-spin`. Nothing
     visibly moved, because the blanket `animation-duration: 0.01ms` above
     collapsed both — but the spin was surviving as a full revolution
     compressed into 0.01ms rather than as no animation at all, which is a
     rule that happens to be harmless rather than a rule that is correct. */
  .bw-footer[data-reveal="hold"].bw-in .bw-signoff__word,
  .bw-signoff__star { animation: none !important; }

  .bw-btn:active { transform: none; }

  /* The primary CTA's hover answer is a lift plus a glow plus a brightening.
     The lift is motion and goes; the other two are not, and they are what is
     left to tell the primary from the secondary — so they stay. */
  .bw-btn--pink:hover,
  .bw-btn--pink:active { transform: none; }

  /* Belt and braces on the reveal fail-safe: the global `animation-duration`
     reset already collapses it to 0.01ms, but its 1.6s delay is what would
     have been felt, and `animation-delay: 0s !important` above kills that. */
  [data-reveal],
  .bw-pricing__grid > .bw-plan { animation: none; }

  .bw-faq__plus { transition: none; }

  .bw-sticky {
    transform: none;
    transition: opacity 0.01ms linear;
  }

  .bw-skip { transition: none; }
}

/* =========================================================================
   22b. Print
   -------------------------------------------------------------------------
   There was no print stylesheet at all, and two things went wrong on paper.
   The fixed skip link printed as ordinary body text — bottom-left of page 1,
   and on top of the first FAQ answer on page 4. And the page is a dark theme:
   with background graphics OFF (the browser default) every ground drops out
   and the headings, prices and fine print land on white at 2.84:1, while with
   them ON the whole document prints as near-black plates.

   Both are the same problem, so both get the same answer: stand every dark
   ground down and restate the inks in the light-section palette this file
   already owns (--bw-ink / --bw-ink-body / --bw-ink-faint, the set the cream
   PRÁCE section uses). No new colour enters, and the two print modes now
   produce the same page.
   ========================================================================= */

@media print {
  :root {
    /* :root and the <meta> both declare `dark`, which makes the UA paint its
       OWN canvas near-black — so with background graphics switched on the
       paper came out black behind transparent grounds. `light` hands the
       canvas back to white without a colour literal entering the file. */
    color-scheme: light;

    --bw-cream: var(--bw-ink);
    --bw-body: var(--bw-ink-body);
    --bw-muted: var(--bw-ink-body);
    --bw-faint: var(--bw-ink-faint);
    --bw-faint-safe: var(--bw-ink-faint);
    --bw-line: var(--bw-ink-faint);
    --bw-field-line: var(--bw-ink-faint);
    --bw-stroke: var(--bw-ink-faint);
    /* Pink is 2.96:1 on white — under AA even for large text. The palette's
       own AA-safe on-pink ink stands in as the accent. */
    --bw-pink: var(--bw-pink-muted-safe);
    --bw-bg: transparent;
    --bw-panel: transparent;
    --bw-nav-bg: transparent;
    --bw-cream-2: transparent;
    --bw-slot-fill: transparent;
    --bw-pink-glow: transparent;
    --bw-pink-ring: transparent;
  }

  /* Screen-only chrome. The sign-off is a ~150px solid wordmark whose whole
     job is to close the scroll; on paper it is a page of ink saying nothing
     the footer above it has not already said. */
  .bw-skip,
  .bw-sticky,
  .bw-marquee,
  .bw-signoff { display: none !important; }

  .bw-header { position: static; }

  body,
  .bw-body {
    background: none;
    color: var(--bw-ink);
  }

  /* Grounds that are declared literally rather than through a token. */
  .bw-works,
  .bw-plan__badge,
  .bw-footer__todo,
  .bw-todo,
  .bw-btn--pink {
    background: none;
    color: var(--bw-ink);
    box-shadow: none;
  }

  /* The accent plan carries its own set; on paper it is a plan like the
     other five, told apart by its badge. */
  .bw-plan--accent {
    --plan-bg: transparent;
    --plan-fg: var(--bw-ink);
    --plan-body: var(--bw-ink-body);
    --plan-muted: var(--bw-ink-body);
    --plan-mark: var(--bw-ink);
    --plan-cta-bg: transparent;
    --plan-cta-fg: var(--bw-ink);
  }

  /* These three read as fills on screen; on paper an outline is what is left
     to say "this is a button / a badge / an unfilled placeholder". */
  .bw-btn--pink,
  .bw-plan__badge,
  .bw-todo { border: 1px solid currentColor; }

  /* .bw-footer__todo .bw-todo (0,2,0) outranks the .bw-todo reset above (0,1,0),
     so the chips inside the launch banner kept their screen ink on the banner's
     screen ground: 1.21:1 on paper, including the grep command the banner tells
     the owner to run. Same specificity, so it lands. */
  .bw-footer__todo .bw-todo {
    background: none;
    color: var(--bw-ink);
    box-shadow: none;
  }

  /* The grid's own ground is what draws the 2px rules BETWEEN the plans, and
     with the plans standing down it printed as one solid black plate. Take the
     ground off and let each card carry its own rule instead. */
  .bw-pricing__grid {
    background: none;
    border-color: var(--bw-ink);
  }

  .bw-plan { border: 1px solid var(--bw-ink); }

  .bw-footer__todo { border: 2px solid currentColor; }

  /* Section 30's blocks join the two rules above for the same two reasons: a
     cream text-stroke is invisible on paper, and a panel fill printed as a
     grey slab wastes toner and drops the contrast of the copy inside it. */
  .bw-empty {
    border: 1px dashed var(--bw-ink);
    background: none;
  }

  .bw-hero__stroke,
  .bw-pricing__stroke,
  .bw-page__stroke,
  .bw-section__stroke { -webkit-text-stroke-color: var(--bw-ink); }
}

/* Apple rule 14: frostier / opaque surfaces. */
@media (prefers-reduced-transparency: reduce) {
  .bw-header {
    background: var(--bw-bg);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

@supports not ((-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px))) {
  .bw-header { background: var(--bw-bg); }
}

/* Outlined display words must not vanish where text-stroke is unsupported. */
@supports not (-webkit-text-stroke-width: 2px) {
  .bw-hero__stroke { color: var(--bw-cream); }
  .bw-pricing__stroke { color: var(--bw-cream); }
  .bw-works__stroke { color: var(--bw-ink); }
  /* Section 30. Same trap: `color: transparent` plus a stroke the browser
     cannot draw is a word that is simply not there. */
  .bw-page__stroke { color: var(--bw-cream); }
  .bw-section__stroke { color: var(--bw-cream); }
  .bw-step__num { color: var(--bw-pink); }
}

/* Forced-colours: keep the outlined words and placeholders legible. */
@media (forced-colors: active) {
  /* box-shadow is dropped in forced colours; without this the focused field
     differed from an unfocused one by 1.5px of forced border colour only. */
  .bw-field__input:focus-visible,
  .bw-field__select:focus-visible,
  .bw-field__textarea:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }

  .bw-hero__stroke,
  .bw-works__stroke,
  .bw-pricing__stroke,
  .bw-page__stroke,
  .bw-section__stroke,
  .bw-step__num { forced-color-adjust: none; }

  .bw-btn { border: 1px solid currentColor; }

  /* Forced colours drop the pink ground these three rely on to shout. A
     border is the one thing that survives, so the launch blockers stay
     visibly marked instead of dissolving into ordinary text. */
  .bw-todo,
  .bw-footer__todo,
  [href^="@@"] { border: 2px solid currentColor; }
}

/* =========================================================================
   24. Booking — the second path to a conversation
   =========================================================================
   The form is still the primary capture: it costs the visitor less than
   putting a stranger in their calendar, which matters while the portfolio
   has nothing in it. This block is the faster lane for someone already
   convinced, so it is deliberately quieter than the pink submit — outline,
   not fill — and sits under the mail line rather than competing with the
   form for the same glance. */

.bw-book {
  margin-top: 34px;
  padding-top: 26px;
  border-top: 1px solid var(--bw-line);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  max-width: 420px;
}

.bw-book__kicker {
  margin: 0;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-3);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--bw-pink);
}

.bw-book__text {
  margin: 0;
  font-size: var(--bw-fs-body-2);
  line-height: 1.6;
  color: var(--bw-body);
}

.bw-book__btn {
  margin-top: 4px;
  padding: 16px 28px;
  font-size: var(--bw-fs-mono-2);
}

.bw-book__note {
  margin: 0;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-fine);
  color: var(--bw-faint-safe);
}

/* The FAQ band's own second line: a text link, not a second button, so the
   band keeps one obvious primary action. `inline-block` never made it a second
   LINE — from 530px to 1920px it sat on the button's own line, 4.1px to the
   right of the notch, with the focus ring landing inside the button's edge.
   A block box with `fit-content` centres it under the button and keeps the
   underline hugging the text; 13px of padding lifts the 19.4px line to a
   45.4px target (WCAG 2.5.8) and the 1px margin keeps the optical gap to the
   button at the designed 14px. */
.bw-cta__second {
  display: block;
  width: fit-content;
  margin: 1px auto 0;
  padding: 13px 6px;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-3);
  color: var(--bw-body);
  text-decoration: underline;
  text-underline-offset: 4px;
  transition: color 150ms ease-out;
}

.bw-cta__second:hover,
.bw-cta__second:focus-visible { color: var(--bw-pink); }

@media (max-width: 760px) {
  .bw-book { margin-top: 28px; padding-top: 22px; max-width: none; }
  .bw-book__btn { width: 100%; text-align: center; }
}

/* =========================================================================
   25. Sign-off — the page ends on its own name
   =========================================================================
   One line, full width, nothing after it. The size is fluid so the word
   spans the gutters at every viewport; the vw coefficient is measured, not
   guessed (see the checks below the rule). line-height 0.78 crops the box
   to the caps — Anton carries a lot of empty space above and below, and at
   this size that dead air reads as a broken layout rather than as leading. */

.bw-signoff {
  container-type: inline-size;
  margin-top: clamp(48px, 7vw, 96px);
  padding-top: clamp(24px, 3vw, 40px);
  border-top: 1px solid var(--bw-line);
  overflow: hidden;              /* a sub-pixel rounding up must never scroll the page */
  line-height: 0;                /* kills the inline-block descender gap under the word */
}

.bw-signoff__word {
  display: block;
  font-family: var(--bw-font-display);
  font-weight: 400;
  /* Sized to FILL the footer's content box, not guessed from the viewport.
     Measured: in Anton, "BLADEWAVE*" renders 4.5615x its own font-size, and
     the box is 100vw minus the two gutters. So
       fill = (100vw - 2*gutter) / 4.5615, taken at 98% for breathing room
            = 21.48vw - 17.2px          (gutter 40px)
     The cap is the value that lands at 1440: past that the word stops
     growing and the footer keeps its own left edge, rather than turning into
     a banner on a 4K display. */
  font-size: min(21.48vw - 17.2px, 292px);
  /* vw counts the classic scrollbar's gutter; the content box does not. That
     mismatch was slicing the asterisk by up to 28% at 320px on Windows and
     Linux, and once the mark turns, the clipped fraction varies with the
     angle — so the silhouette breathed against the clip edge on a 26s cycle.
     Container units measure the box that actually exists. Same 21.48
     coefficient, same 98% of advance width, no gutter arithmetic, and it
     collapses the mobile branch into the same rule. */
  line-height: 0.78;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: var(--bw-cream);
  white-space: nowrap;
  user-select: none;
}

.bw-signoff__star {
  color: var(--bw-pink);
  /* `transform` does not apply to a non-replaced inline box, so the asterisk
     has to become one that it does apply to. `inline-block` is the smallest
     such change and it is metrically free: measured at 320 / 390 / 768 / 1440
     / 2560, the container height, the word's line box, its baseline position
     and the star's own advance width are byte-identical before and after
     (drift 0.000px at every width). The advance is unchanged because the
     inherited -0.02em letter-spacing still lands after the glyph either way;
     the line box is unchanged because an inline-block whose only content is
     one line of the same inherited line-height puts its baseline exactly
     where the strut already had it. `vertical-align` is spelled out because
     it is the one property that would break that equality if it ever drifted
     from its initial value. */
  display: inline-block;
  vertical-align: baseline;
  /* The rotation centre is the asterisk's INK centre, not its box centre. A
     glyph sits high and left inside its em box — here the ink centre is
     0.24em down a 0.78em box — so `transform-origin: center` makes the star
     ORBIT a point ~106px away instead of turning in place. That is not a
     subtle difference at this size; the first cut of this rule had the
     origin wrong and the sampled frames showed the mark tracing a wide arc
     down and across the word.

     The two numbers are measured off rendered pixels, not derived, because
     Chrome snaps the baseline to whole pixels and the arithmetic answer is
     off by up to 1px. At deviceScaleFactor 4, across fifteen widths from
     320 to 2560, the pink ink's centroid sits at
       x = 0.22526 .. 0.22627em  from the box's left edge (which is also the
                                 glyph origin — an inline-block starts at the
                                 pen, so no side bearing to subtract)
       y = 0.22825 .. 0.24344em  from the box's top edge
     The spread is Chrome's integer baseline snapping, nothing else. Taking
     0.226 / 0.24 the worst residual over those fifteen widths is 0.9px, on a
     mark 121px across — a sub-pixel wobble on a wheel that takes 26s to come
     round, which is to say none.

     The ink measures 0.4162em wide by 0.4166em tall: a circle to within
     0.1%, which is what keeps the spin cheap in space. Seven arms reaching
     the same radius means turning the mark barely changes the size of its
     bounding box — swept at 10-degree steps it grows 3.3% at most (121.7px
     to 125.7px at 1440), against a resting clearance to the container's clip
     edge of 25px right / 47px top / 94px bottom. The tightest case in the
     whole range is 320px wide, where the clearance is still 5.0px right,
     20.8px top and 18.7px bottom while the box grows by 1px. Measured at
     every 10 degrees of a full revolution at nine widths from 320 to 2560:
     the star never touches the clip, and the document's horizontal overflow
     is 0px at every angle. */
  transform-origin: 0.226em 0.24em;
}

/* ---- Motion --------------------------------------------------------------
   Two moving parts, both borrowed rather than invented.

   1. THE MARK TURNS. Voidex's giant wordmark carries no animation at all;
      the only thing moving in that footer is the solid square beside the
      word, on `vx-spin 26s linear infinite`. The asterisk is BladeWave's
      mark the way the square is Voidex's, so it gets the same constant, at
      the same 26s, linear, forever. Nothing else about the word moves.

   2. THE WORD ARRIVES ONCE. Voidex's reveal vocabulary for type is
      `vx-mask`: translateY(112%) → 0 behind a clipped box. This container is
      already exactly that shape — `overflow: hidden`, a border-top and
      `padding-top` above the word, `line-height: 0` around it — so the word
      rises out from under its own rule and stops. 112% is Voidex's number
      and it is also sufficient here: the word's ink overshoots its 0.78em
      line box by 0.0469em at the top, so full concealment needs 106.0% plus
      up to ~2% of sub-pixel rounding at the smallest size. 112% clears it at
      every width with margin, verified frame-by-frame at t=0.

   Duration and easing are the page's own: 0.9s / var(--bw-ease), the exact
   pair `bw-hero-in` uses. The page opens and closes on the same gesture, and
   the ease is the same family as Voidex's --ease-set — fast off the mark,
   long settle — which is what makes a 250px rise read as decisive rather
   than as a slide.

   ---- Why the trigger is the FOOTER and not the sign-off ----------------
   The word's resting state is visible, and the animation is what hides it.
   So between "the word is on screen" and "the class lands" there is a window
   in which the browser can paint the word at rest and then blink it out —
   and that window is exactly as wide as the observer is late.

   Watching `.bw-signoff` made it far too narrow. The observer reveals at
   intersectionRatio 0.15, and 0.15 of the sign-off box is 40px at 1440 while
   its padding-top is 41px: a margin of ONE PIXEL of scroll. Measured, at
   320x568, on a jump straight to the bottom of the page: two full frames of
   BLADEWAVE* painted at rest, then blank, then the rise — a 33ms blink,
   caught on a 60fps capture of the real page.

   Moving the subscription up to the footer fixes it at the source rather
   than papering over it. The footer is 612-801px tall, so it reaches the
   viewport long before the word does, and `reached()` in main.js lets an
   element taller than the viewport through at ratio ~0 as well. The margin
   measured across eight viewports and both pages is 371-719px of scroll,
   against 1-11px before. The word is hidden while it is still most of a
   screen below the fold, every time.

   That leaves one case a trigger cannot cover: arriving at the bottom in a
   single jump (the End key, a restored scroll position, a hard flick), where
   there is no earlier frame to fire in. main.js handles that one on its own
   terms — see `bw-signoff-seen` below.

   ---- Why this cannot strand the word ----------------------------------
   The resting style declares NO opacity and NO transform on the word. The
   hidden state exists nowhere in the stylesheet except inside the `from`
   keyframe of an animation that always ends at `translateY(0)`, and that
   animation carries no `fill-mode`, so it holds nothing after it finishes
   and nothing before it starts. The word is therefore visible in every state
   where the animation does not run to completion, which is the whole list:

     JS off / main.js 404 / main.js throws before initReveal — no `.bw-in`
       is ever added, so no animation is ever composed. Word visible, still.
     IntersectionObserver missing — main.js's `revealRest()` adds `.bw-in`
       immediately; the animation runs once and ends visible.
     IntersectionObserver constructor throws — the 1200ms fail-safe in
       initReveal is armed BEFORE the constructor, so `revealRest()` still
       fires. Same result.
     prefers-reduced-motion — killed outright in section 22, word at rest.

   There is no path where the word is left mid-flight, because nothing but a
   live, running animation can move it, and every live animation here ends
   where the resting style already is. This is the opposite arrangement from
   [data-reveal], which hides by default and is rescued by two fail-safes;
   the wordmark is too big to be rescued, so it is never hidden in the first
   place. That is also what `data-reveal="hold"` means on the footer: take
   the observer's timing, keep your own appearance. */

/* An element that subscribes to the reveal observer for its TIMING only and
   declines the reveal itself. It is styled as finished from the first frame,
   and whatever motion it wants happens on a descendant under `.bw-in`. */
[data-reveal="hold"] {
  opacity: 1;
  transform: none;
  transition: none;
  will-change: auto;
}

@keyframes bw-signoff-rise {
  from { transform: translateY(112%); }
  to   { transform: translateY(0); }
}

@keyframes bw-signoff-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* No fill-mode on purpose — see above. `to` is the resting state, so letting
   the animation drop its effect at the end is both correct and cheaper: the
   compositor layer the transform forces is released the moment it lands,
   rather than being pinned on a 2480x228 element for the rest of the
   session. Confirmed at runtime: once the rise ends, getAnimations() on the
   word returns []. */
.bw-footer[data-reveal="hold"].bw-in .bw-signoff__word {
  animation: bw-signoff-rise 0.9s var(--bw-ease);
}

/* The one case the trigger cannot reach: the visitor arrived at the bottom
   in a single jump and the word was already on screen when the class landed.
   Hiding it now to rise would be a blink, and it would also be wrong —
   an entrance is for something entering, and this has already entered.
   main.js sets `bw-signoff-seen` in that case and the word simply stays
   where it is. The mark still starts turning. */
.bw-footer[data-reveal="hold"].bw-in.bw-signoff-seen .bw-signoff__word {
  animation: none;
}

/* NOT gated on the reveal class, deliberately, and the reference is the
   reason: in Voidex the mark's spin is declared on the element itself and
   runs whether or not a script does. Gating it here made the one thing that
   was asked for after seeing that page the one thing that disappears when
   main.js 404s or throws — measured frozen at 0 degrees in both states.
   The gate was also failing at its own stated job: with IntersectionObserver
   missing or throwing, `.bw-in` lands at load and the mark span the whole
   visit anyway, 9141px below the fold. A guard that does not hold in the
   paths it was written for is not buying the cost it charges. Off-screen
   compositor animations are throttled by the engine, which is the mechanism
   that was actually wanted. */
.bw-signoff__star {
  animation: bw-signoff-spin 26s linear infinite;
}

@media (max-width: 760px) {
  .bw-signoff { margin-top: 40px; padding-top: 20px; }
  /* Narrow-gutter fallback, for engines without container units only. */
  .bw-signoff__word { font-size: calc(21.48vw - 8.6px); }
}

/* The real rule. Declared after both fallbacks so it wins on order, and
   guarded so an engine without container units keeps the vw arithmetic. */
@supports (font-size: 1cqw) {
  .bw-signoff__word { font-size: min(21.48cqw, 292px); }
}

/* =========================================================================
   26. Work clips
   =========================================================================
   .bw-work__video and .bw-work__media were already declared in the works
   section, reserved for exactly this. All that was missing is the ground the
   poster sits on while it decodes. */

.bw-work__video { background: var(--bw-cream-2); }

/* Now that viewport-fit=cover is set, content can reach the physical edge of the
   screen — which in landscape is behind the notch. The gutter absorbs it. env()
   resolves to 0 on every device without one, so this changes nothing there, and
   the carousel's -1*gutter bleed follows the same value automatically. */
@supports (padding: env(safe-area-inset-left)) {
  :root {
    --bw-gutter: max(40px, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px));
    --bw-gutter-sm: max(20px, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px));
  }
}

/* =========================================================================
   28. The exclusion line
   =========================================================================
   Set apart from the lead above it, because it is doing a different job:
   the lead sells the price list, this one fences it. A left rail in the
   accent is the same device .bw-form__echo already uses for "this applies
   to what follows". */

.bw-pricing__exclusion {
  margin: 18px 0 0;
  padding: 12px 0 12px 16px;
  border-left: 3px solid var(--bw-pink);
  max-width: 62ch;
  font-size: var(--bw-fs-body-2);
  line-height: 1.6;
  color: var(--bw-body);
}

.bw-pricing__exclusion strong {
  font-weight: 700;
  color: var(--bw-cream);
}

/* =========================================================================
   29. Sourcing lines under the stat rows
   =========================================================================
   Deliberately quiet. The numbers do the shouting; this line only has to be
   readable by someone who stopped to check them — which is exactly the
   sceptical buyer worth converting. --bw-faint-safe, not --bw-faint: the
   latter is 3.32:1 on this ground and fails AA. */

.bw-stats__source,
.bw-about__source {
  margin: 18px 0 0;
  max-width: 60ch;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-fine);
  line-height: 1.6;
  color: var(--bw-faint-safe);
}

.bw-stats__source strong,
.bw-about__source strong {
  font-weight: 700;
  color: var(--bw-body);
}

@media (max-width: 760px) {
  .bw-stats__source,
  .bw-about__source { margin-top: 14px; }
}

/* =========================================================================
   30. Multi-page chrome — page header, shared page blocks, footer sitemap
   =========================================================================
   The site stopped being one document. Everything below exists because a page
   that is not the home page needs three things the landing page never did:

     * a header block of its own, so the visitor knows which page they are on
       before they have read a word of body copy;
     * a sitemap in the footer, because the main nav hides five of its six
       items below 761px and on a phone the footer is then the ONLY complete
       route to the other pages;
     * an honest way to say "there is nothing here yet" — the case-study pages
       have no case studies, and an empty state is the only truthful thing to
       put there.

   No new colour, no new family, no new spacing unit: every value is a token
   that already existed. The type steps are the ones the home page already
   uses, one clean step apart — page title --bw-fs-display-xl, section title
   --bw-fs-display, section h3 --bw-fs-plan.

   LEADING, and this has bitten this project three times: the display face is
   Anton and Czech caps carry carons, acutes and the ring of Ů. Single-line
   headings can use --bw-lh-display (0.95); anything that can wrap needs
   --bw-lh-display-multi (1.15) and anything containing Ů or Ú on a wrapped
   line needs --bw-lh-display-ring (1.25). The defaults below are the SAFE
   ones, because the copy is not written yet.
   ========================================================================= */

/* ---- Page header --------------------------------------------------------
   Sits directly under the sticky header and opens every sub-page. No bottom
   hairline: the first .bw-section under it draws its own top rule, and the
   pair drew two lines 60px apart — the same mistake .bw-legal__head fixed. */

.bw-page {
  padding: 84px var(--bw-gutter) 68px;
}

.bw-page__inner {
  max-width: var(--bw-max);
  margin: 0 auto;
}

.bw-page__kicker {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-3);
  color: var(--bw-pink);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0 0 18px;
}

.bw-page__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-display-xl);
  line-height: var(--bw-lh-display-ring);
  letter-spacing: var(--bw-track-display);
  text-transform: uppercase;
  margin: 0 0 22px;
}

/* The outlined word, exactly as .bw-pricing__stroke does it on this ground. */
.bw-page__stroke {
  color: transparent;
  -webkit-text-stroke: 2px var(--bw-cream);
}

.bw-page__lead {
  font-size: var(--bw-fs-lead);
  color: var(--bw-body);
  line-height: var(--bw-lh-loose);
  max-width: 620px;
  margin: 0;
}

.bw-page__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
  margin-top: 34px;
}

/* One quiet line for a qualifier under the actions — "odpovídáme do 24 hodin",
   "ceny jsou bez DPH". --bw-faint-safe, never --bw-faint: 3.32:1 fails AA on
   this ground and the palette's own next step up does not. */
.bw-page__meta {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-fine);
  line-height: 1.6;
  color: var(--bw-faint-safe);
  max-width: 60ch;
  margin: 22px 0 0;
}

/* ---- Generic content section --------------------------------------------
   The same shape .bw-pricing and .bw-faq already have: a hairline, the page
   rhythm, a capped inner column. --flush drops the rule for a section that
   follows the page header directly, where the header's own air is enough. */

.bw-section {
  border-top: var(--bw-hair) solid var(--bw-line);
  padding: var(--bw-sec-y) var(--bw-gutter);
}

.bw-section--flush { border-top: 0; }

.bw-section__inner {
  max-width: var(--bw-max);
  margin: 0 auto;
}

.bw-section__head { margin-bottom: 52px; }

.bw-section__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-display);
  line-height: var(--bw-lh-display-multi);
  letter-spacing: var(--bw-track-display);
  text-transform: uppercase;
  margin: 0 0 16px;
}

.bw-section__stroke {
  color: transparent;
  -webkit-text-stroke: 2px var(--bw-cream);
}

.bw-section__lead {
  font-size: var(--bw-fs-lead-3);
  color: var(--bw-body);
  line-height: 1.5;
  max-width: 560px;
  margin: 0;
}

.bw-section__h3 {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-plan);
  line-height: var(--bw-lh-tight);
  letter-spacing: var(--bw-track-display-sm);
  text-transform: uppercase;
  margin: 0 0 14px;
}

.bw-section__text {
  color: var(--bw-body);
  font-size: var(--bw-fs-body);
  line-height: var(--bw-lh-loose);
  max-width: 62ch;
  margin: 0 0 16px;
}

.bw-section__text:last-child { margin-bottom: 0; }

.bw-section__text strong {
  color: var(--bw-cream);
  font-weight: 700;
}

/* ---- Two columns --------------------------------------------------------
   grid, not flex: `minmax(0, …)` is what stops a long unbreakable string in
   one column from pushing the other off the page, and 1fr alone does not —
   the default minimum is `auto`, i.e. the content's own min width. */

.bw-split {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: var(--bw-col-gap);
  align-items: start;
}

.bw-split--even { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* Both halves of the belt-and-braces. `minmax(0, …)` above caps the TRACK;
   this caps the ITEM, whose own `min-width` still defaults to `auto` — i.e. to
   its content's minimum. Without it a long URL or an unbreakable Czech
   compound in one column pushes the other one off the page. */
.bw-split__main,
.bw-split__aside { min-width: 0; }

/* A sidebar that has to sit ahead of its column on a phone (a price box above
   the copy it prices, say) is ordered here rather than in the markup, so the
   reading order in the DOM stays the one a screen reader should hear. */
.bw-split--aside-first .bw-split__aside { order: -1; }

@media (min-width: 761px) and (max-width: 1100px) {
  .bw-split { gap: 48px; }
}

@media (max-width: 900px) {
  .bw-split,
  .bw-split--even { grid-template-columns: minmax(0, 1fr); }
  .bw-split { gap: 40px; }
  .bw-split--aside-first .bw-split__aside { order: 0; }
}

/* ---- Empty state --------------------------------------------------------
   For the case-study pages. There is no performance track record — no
   campaign, no CPL, no ROAS, no spend, no client — so the pages that would
   show those ship saying exactly that. Dashed, because a dashed edge reads as
   "reserved space" and a solid one reads as "a card with content in it": the
   same signal .bw-slot already uses for a missing image.

   Dark ground only. On the cream .bw-works section these tokens invert into
   an unreadable box; use --bw-ink / --bw-ink-body / --bw-cream-2 there. */

.bw-empty {
  border: var(--bw-hair) dashed var(--bw-field-line);
  background: var(--bw-panel);
  padding: 40px 32px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.bw-empty__title {
  font-family: var(--bw-font-mono);
  font-weight: 700;
  font-size: var(--bw-fs-mono);
  color: var(--bw-pink);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1.35;
  margin: 0;
}

.bw-empty__text {
  font-size: var(--bw-fs-body);
  color: var(--bw-body);
  line-height: var(--bw-lh-loose);
  max-width: 62ch;
  margin: 0;
}

.bw-empty__note {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-fine);
  color: var(--bw-faint-safe);
  line-height: 1.6;
  max-width: 60ch;
  margin: 0;
}

.bw-empty__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 6px;
}

/* ---- Footer sitemap -----------------------------------------------------
   The links are plain .bw-footer__link on purpose. An override here would
   need (0,2,0) specificity and would then out-rank `.bw-footer__link:hover`
   at (0,1,1) — the pink hover would simply stop happening, silently, which is
   the exact failure section 19 documents for `a:hover` and .bw-btn. The row
   is separated by a hairline instead, the same language the identity block
   below it already speaks. */

.bw-footer__sitenav {
  border-bottom: var(--bw-hair) solid var(--bw-line);
  padding-bottom: 22px;
}

.bw-footer__sitelist {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
}

.bw-footer__site-item { display: flex; }

/* ---- You are here -------------------------------------------------------
   aria-current carries this to assistive tech; these two rules carry it to
   everyone else. Colour alone would be the only visual cue, so it is colour
   AND a pink underline. Both selectors are (0,1,1) — the same weight as
   `.bw-nav__link:hover` — and they are declared after it, so hovering the
   current page still turns it pink and nothing is silently out-ranked. */

.bw-nav__link[aria-current="page"],
.bw-footer__link[aria-current="page"] {
  color: var(--bw-cream);
  text-decoration: underline;
  text-decoration-color: var(--bw-pink);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.35em;
}

/* ---- Small viewports ----------------------------------------------------
   The left/right gutters are handled by the [data-bw-pad] list in section 21;
   only the vertical scale and the two display sizes are answered here. */

@media (max-width: 760px) {
  .bw-page { padding-top: 52px; padding-bottom: 44px; }
  .bw-page__lead { font-size: var(--bw-fs-lead-2); }
  .bw-page__actions { gap: 12px; }
  .bw-section__head { margin-bottom: 34px; }
  .bw-empty { padding: 28px 22px; }
  .bw-footer__sitelist { gap: 18px; }
}

/* A full-width action on a phone: two side-by-side buttons at 320px leave
   each one 140px of box for a 12-character label. */
@media (max-width: 480px) {
  .bw-page__actions,
  .bw-empty__actions { flex-direction: column; align-items: stretch; }

  .bw-page__actions > .bw-btn,
  .bw-empty__actions > .bw-btn { text-align: center; }
}

/* =========================================================================
   31. SLUŽBY — the services hub (sluzby.html)
   =========================================================================
   APPEND-ONLY SECTION. Nothing above this line was edited. Every value is an
   existing token; no new hex, no new family, no new type step.

   The page carries the owner's whole SERVICES node in one document: a header,
   a two-item chooser, then the two branches as sections with their own ids
   (#content-social, #performance-growth) so the tree's three nodes all keep
   an address. Three blocks were needed for that and none of them existed:

     .bw-branch   the chooser. Two link cards, no headings inside — the page
                  outline has to stay h1 -> h2 -> h3 and a chooser is
                  navigation, not content.
     .bw-offer    one Content & Social service, expanded into what it is /
                  what you get / what we need from you. The two lists are the
                  reason the block exists: the home page's one-line card
                  cannot say what the client has to supply, and that is the
                  half of the deal that decides whether the work happens.
     .bw-phase    one Performance Growth phase. Six of these in an <ol>, with
                  ordinals and a hairline CONNECTOR between them, because the
                  brief is explicit that these are six phases of running one
                  ad account and not six products. A price-list reading is the
                  failure mode, so the block is built to read as a sequence:
                  ordered list, ordinals in reading order, a line joining
                  them, and no price anywhere.
     .bw-handoff  the end-of-branch hand-off panel. Content & Social ends at
                  the price list, Performance Growth ends at the sales page,
                  and a branch that just stops is a dead end.

   THE THREE THINGS THIS FILE HAS SHIPPED BUGS ON, answered at the bottom of
   this section rather than left to chance: an outlined ordinal is
   `color: transparent` plus a stroke, so it VANISHES where text-stroke is
   unsupported, in forced colours, and on paper. All three are covered.
   ========================================================================= */

/* ---- Branch chooser -----------------------------------------------------
   Whole-card links. `align-items` is left at grid's `stretch` so both cards
   share a height, and `.bw-branch__cue` takes `margin-top: auto` so the two
   cues line up even when one card's copy runs a line longer. */

.bw-branch__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
}

.bw-branch__item { display: flex; min-width: 0; }

.bw-branch__link {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  min-width: 0;
  padding: 28px 30px 24px;
  background: var(--bw-panel);
  border: 1.5px solid var(--bw-line);
  transition:
    border-color var(--bw-t-hover) ease-out,
    background-color var(--bw-t-hover) ease-out;
}

/* (0,2,0) against the base `a { color }` at (0,0,1) — nothing here is being
   silently out-ranked, because no `a:hover` rule exists in this file at all
   (§19 documents why: adding one would out-rank .bw-btn's own states). */
.bw-branch__link:hover { border-color: var(--bw-pink); }

.bw-branch__num {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-body-2);
  color: var(--bw-pink);
}

.bw-branch__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-price);
  /* Can wrap at narrow widths and carries no ring, so --multi, not --display. */
  line-height: var(--bw-lh-display-multi);
  letter-spacing: var(--bw-track-display-sm);
  text-transform: uppercase;
}

.bw-branch__text {
  font-size: var(--bw-fs-body-2);
  color: var(--bw-body);
  line-height: var(--bw-lh-body);
}

.bw-branch__cue {
  margin-top: auto;
  padding-top: 12px;
  font-family: var(--bw-font-mono);
  font-weight: 700;
  font-size: var(--bw-fs-mono-3);
  color: var(--bw-pink);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* ---- One expanded service ----------------------------------------------
   The same grid language .bw-service already speaks on the home page — a mono
   index in a fixed first column, content in the second — one step wider,
   because the content here is a heading plus two lists instead of one line.
   `align-items: baseline` puts the index on the title's first baseline; the
   second column is a block, so its first baseline IS the title's. */

.bw-offer {
  display: grid;
  grid-template-columns: 90px minmax(0, 1fr);
  gap: 24px;
  align-items: baseline;
  padding: 44px 0;
  border-top: var(--bw-hair) solid var(--bw-line);
}

.bw-offer:first-child {
  border-top: 0;
  padding-top: 0;
}

.bw-offer:last-child { padding-bottom: 0; }

.bw-offer__num {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-body-2);
  color: var(--bw-muted);
}

.bw-offer__body { min-width: 0; }

.bw-offer__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-service);
  /* Wraps at every width below ~1100px ("SPRÁVA SOCIÁLNÍCH SÍTÍ" is 22
     characters of Anton caps with three acutes), so --multi is the floor.
     No Ů in any of the four titles, so --ring is not needed. */
  line-height: var(--bw-lh-display-multi);
  /* Positive 0.5px, the value the source declares for a služby heading and
     the one .bw-service__title already carries. */
  letter-spacing: 0.5px;
  text-transform: uppercase;
  margin: 0 0 16px;
}

.bw-offer__text {
  font-size: var(--bw-fs-lead-3);
  color: var(--bw-body);
  line-height: var(--bw-lh-body);
  max-width: 62ch;
  margin: 0 0 30px;
}

.bw-offer__cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 26px 44px;
}

.bw-offer__col { min-width: 0; }

.bw-offer__label {
  font-family: var(--bw-font-mono);
  font-weight: 700;
  font-size: var(--bw-fs-mono-3);
  color: var(--bw-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1.4;
  margin: 0 0 16px;
  padding-bottom: 12px;
  border-bottom: var(--bw-hair) solid var(--bw-line);
}

.bw-offer__list {
  display: grid;
  gap: 12px;
}

/* Same shape as .bw-assure__item: the mark is pulled out of the flow so a
   wrapped second line hangs under the first character of the text, not under
   the slash. */
.bw-offer__item {
  position: relative;
  padding-left: 18px;
  font-size: var(--bw-fs-item);
  color: var(--bw-body);
  line-height: var(--bw-lh-snug);
}

.bw-offer__mark {
  position: absolute;
  left: 0;
  font-family: var(--bw-font-mono);
  color: var(--bw-pink);
}

.bw-offer__note {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-fine);
  line-height: 1.6;
  /* --bw-faint-safe, never --bw-faint: 3.32:1 fails AA on this ground. */
  color: var(--bw-faint-safe);
  max-width: 60ch;
  margin: 26px 0 0;
}

/* ---- One phase of the ad account ---------------------------------------
   An <ol> with `list-style: none` (base reset), so the ordinal on screen is
   the .bw-phase__num span and NOT a list marker. That span is deliberately
   left readable by assistive tech — it is the sequence, which is the whole
   point of the block — while the decorative "/01" indexes elsewhere on the
   page are aria-hidden.

   The connector is drawn from the RAIL, not from the number: the rail is a
   grid item and grid's default `stretch` makes it exactly the row's height,
   which is the one box that knows where the next phase starts. Anchoring it
   to the number instead would have meant hard-coding the body's height. */

.bw-phase {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: 30px;
  padding: 0 0 34px;
}

.bw-phase:last-child { padding-bottom: 0; }

.bw-phase__rail { position: relative; }

.bw-phase__num {
  display: block;
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-num);
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1.5px var(--bw-pink);
  /* Outlined type needs POSITIVE tracking: the 1.5px stroke grows 0.75px
     outward on each side and eats the sidebearings, exactly as
     .bw-step__num documents. */
  letter-spacing: 0.02em;
}

/* left: 25px, measured, not guessed: two Anton digits at 60px/1 with the
   0.02em tracking measure 51.89px of ink from the column's left edge, so the
   optical centre is 25.95px. The line starts below the glyph box and runs
   into this block's own bottom padding, so it stops a fixed 20px short of the
   next ordinal instead of either touching it or stopping in mid-air. */
.bw-phase:not(:last-child) .bw-phase__rail::after {
  content: "";
  position: absolute;
  left: 25px;
  top: 68px;
  bottom: -20px;
  width: var(--bw-hair);
  background: var(--bw-line);
}

.bw-phase__body { min-width: 0; }

.bw-phase__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-plan);
  line-height: var(--bw-lh-tight);
  letter-spacing: var(--bw-track-display-sm);
  text-transform: uppercase;
  margin: 0 0 14px;
}

.bw-phase__text {
  font-size: var(--bw-fs-body);
  color: var(--bw-body);
  line-height: var(--bw-lh-loose);
  max-width: 62ch;
  margin: 0;
}

.bw-phase__need {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-fine);
  line-height: 1.6;
  color: var(--bw-faint-safe);
  max-width: 60ch;
  margin: 14px 0 0;
}

.bw-phase__need-label {
  color: var(--bw-pink);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* ---- End-of-branch hand-off --------------------------------------------
   Same two-column shape as .bw-works__offer, on the dark ground. The title is
   a <p>, not a heading: this is a sign-post at the end of a branch and giving
   it an h3 would put it on the same level as the services above it in the
   document outline. */

.bw-handoff {
  margin-top: 60px;
  padding: 32px 36px;
  background: var(--bw-panel);
  border: 1.5px solid var(--bw-line);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 48px;
  row-gap: 0;
}

.bw-handoff__kicker {
  grid-column: 1;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-3);
  color: var(--bw-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0 0 12px;
}

.bw-handoff__title {
  grid-column: 1;
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-price);
  line-height: var(--bw-lh-display-multi);
  letter-spacing: var(--bw-track-display-sm);
  text-transform: uppercase;
  margin: 0 0 12px;
}

.bw-handoff__text {
  grid-column: 1;
  font-size: var(--bw-fs-body-2);
  color: var(--bw-body);
  line-height: var(--bw-lh-body);
  max-width: 62ch;
  margin: 0;
}

/* `span 3`, not `1 / -1`: the grid has no EXPLICIT rows, so -1 resolves to
   line 1 and the button rides up level with the kicker — the same trap
   .bw-works__offer-cta documents. */
.bw-handoff__cta {
  grid-column: 2;
  grid-row: 1 / span 3;
  align-self: center;
  justify-self: end;
  white-space: nowrap;
  /* .bw-btn with neither --sm nor --lg renders at zero padding; sized like
     .bw-works__offer-cta, which is the same kind of section-level action. */
  padding: 17px 30px;
}

/* ---- Small viewports ----------------------------------------------------
   The left/right gutters come from the [data-bw-pad] list in §21 via .bw-page
   and .bw-section, both of which are already on it. Only the internal grids
   are answered here. */

@media (max-width: 900px) {
  .bw-branch__list { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 760px) {
  .bw-branch__link { padding: 24px 22px 22px; }

  .bw-offer {
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
    padding: 32px 0;
  }

  .bw-offer__text { margin-bottom: 24px; }
  .bw-offer__cols { grid-template-columns: minmax(0, 1fr); gap: 24px; }

  /* Single column: the connector has nothing to connect, and the ordinals
     plus the rules between phases carry the sequence on their own. The <ol>
     carries it for assistive tech either way. */
  .bw-phase {
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
    padding: 26px 0;
    border-top: var(--bw-hair) solid var(--bw-line);
  }

  .bw-phase:first-child { border-top: 0; padding-top: 0; }
  .bw-phase:not(:last-child) .bw-phase__rail::after { display: none; }
  .bw-phase__num { font-size: 46px; }

  .bw-handoff {
    grid-template-columns: minmax(0, 1fr);
    padding: 26px 22px;
    margin-top: 44px;
  }

  .bw-handoff__cta {
    grid-column: 1;
    grid-row: auto;
    justify-self: start;
    margin-top: 22px;
    white-space: normal;
  }
}

/* Two 15-character mono labels do not fit side by side in 280px of content
   box, and neither does one of them beside its own padding. Same answer §30
   gives .bw-page__actions at this width. */
@media (max-width: 480px) {
  .bw-handoff__cta {
    justify-self: stretch;
    text-align: center;
  }
}

/* ---- The three ways an outlined ordinal disappears ----------------------
   .bw-phase__num is `color: transparent` plus a stroke. Section 30 already
   documents that this exact construction has shipped as an invisible word
   three times in this project. It is answered here for the new element in
   the same three places §22b, the @supports fallback and the forced-colours
   block answer it for the existing ones. */

@media print {
  /* Pink becomes --bw-pink-muted-safe on paper (§22b), which is dark enough
     to read — but the ground under it is white, so the ordinal is restated in
     the light-section ink like every other stroked word. */
  .bw-phase__num { -webkit-text-stroke-color: var(--bw-ink); }

  /* Panel fills print as grey slabs and cost contrast for no information. */
  .bw-branch__link,
  .bw-handoff {
    background: none;
    border: 1px solid var(--bw-ink-faint);
  }

  .bw-phase:not(:last-child) .bw-phase__rail::after { background: var(--bw-ink-faint); }
}

/* A stroke the browser cannot draw plus transparent ink is a number that is
   simply not there. */
@supports not (-webkit-text-stroke-width: 1.5px) {
  .bw-phase__num { color: var(--bw-pink); }
}

@media (forced-colors: active) {
  .bw-phase__num { forced-color-adjust: none; }

  /* Forced colours drop the --bw-panel ground both cards rely on to read as
     cards; the border is what survives. */
  .bw-branch__link,
  .bw-handoff { border: 1px solid currentColor; }
}

/* ---- Wide viewports: the phase's "od vás" line moves beside the copy ----
   .bw-phase__text is capped at 62ch for readability, so above ~1100px the
   right 40% of six stacked phases was empty ground. The requirement line is
   the natural tenant: it is short, it is the half of the deal the client owns,
   and putting it beside the copy instead of under it makes that split visible
   rather than merely stated. Below this width it goes back under the copy,
   where a 60ch mono line still has room. */

@media (min-width: 1101px) {
  .bw-phase__body {
    display: grid;
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
    column-gap: var(--bw-col-gap);
    align-items: start;
  }

  /* Spans both tracks; the copy and the requirement then auto-place into
     row 2, column 1 and column 2. */
  .bw-phase__title { grid-column: 1 / -1; }
  .bw-phase__need { margin-top: 0; padding-top: 5px; }
}

/* ---- Mobile gutter for §30's two page blocks ----------------------------
   MEASURED BUG, not a preference. §21's [data-bw-pad] list sets
   `padding-left/right: var(--bw-gutter-sm)` (20px) below 760px and §30's
   comment says the gutters "are handled by the [data-bw-pad] list in section
   21" — but they are not. §30 declares `.bw-page { padding: 84px
   var(--bw-gutter) 68px }` as a SHORTHAND at line ~3577, i.e. AFTER the pad
   list at ~2700, at the same (0,1,0) specificity, and its own 760px block
   only redeclares padding-top and padding-bottom. Source order wins, so the
   left/right stay at 40px.

   The result is visible on any sub-page at 390px: .bw-page and .bw-section
   start at x=40 while .bw-cta--mid and .bw-footer on the SAME page start at
   x=20, and a 320px screen loses 25% of its width to gutters. Measured on
   sluzby.html:
       320px  page 40 / section 40 / cta--mid 20 / footer 20

   Longhand, after both, so it lands without touching either rule. This is an
   append, not an edit — but it does affect every page that uses .bw-page or
   .bw-section, which is every sub-page built from _template.html. */

@media (max-width: 760px) {
  .bw-page,
  .bw-section {
    padding-left: var(--bw-gutter-sm);
    padding-right: var(--bw-gutter-sm);
  }
}

/* =========================================================================
   32. CENÍK / O NÁS / KONTAKT — three things section 30 did not need
   =========================================================================
   NUMBERING: four pages were appended to this file at the same time and all
   four first numbered themselves 31. The convention the blocks settled on is
   document order, so SLUŽBY is 31, this one is 32, PERFORMANCE 33 and WORK
   34. The name identifies the block; the digit is provisional until someone
   runs a single renumbering pass.

   Appended, never edited above: every rule here is new, and each one exists
   because one of these three pages hit a case the single-document layout
   never could. Tokens only, no new colour.
   ========================================================================= */

/* ---- The contact section as a page header -------------------------------
   kontakt.html leads with .bw-contact instead of .bw-page: two display-size
   headings stacked would compete, and a .bw-page above the form would push
   the first field another ~300px down on the one page whose whole job is
   that field. What .bw-contact was never designed for is being FIRST — its
   `border-top` then lands directly under the sticky site header and reads as
   an underline of the navigation, not as a section rule. Same specificity as
   `.bw-contact` (0,1,0) and declared after it, so it lands without !important
   and without touching the rule the home page uses. */

.bw-contact--first { border-top: 0; }

/* ---- The sixth cell of a five-plan grid ---------------------------------
   .bw-pricing__grid is three columns and there are five plans, so the last
   cell is empty — and the grid's own ground is --bw-cream, which means that
   empty cell paints as a 411x559px cream slab that reads as a card that
   failed to load. On the home page it is a blemish; on the page whose entire
   job is the price list it is the largest single object in the section.

   It is NOT a sixth plan and deliberately carries no .bw-plan class: the
   plan count is asserted by the hand-off test, the reveal stagger is written
   for .bw-plan children, and a card with no price in a price grid would be
   the third kind of lie this project has already refused. It is a panel with
   the question the grid provokes, and it uses `data-reveal` — the document's
   standard reveal, with both of section 3's no-JS guards already covering
   it — rather than a private copy of the stagger that would need its own.

   Centred, not top-aligned: it is a note beside the cards, and matching
   their kicker line would only advertise that it is a card missing a price. */

.bw-ask {
  background: var(--bw-panel);
  color: var(--bw-cream);
  padding: 38px 32px;          /* .bw-plan's box, so the cell measures alike */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 14px;
}

.bw-ask__kicker {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-2);
  color: var(--bw-pink);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0;
}

.bw-ask__title {
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-plan);
  line-height: var(--bw-lh-tight);
  letter-spacing: var(--bw-track-display-sm);
  text-transform: uppercase;
  margin: 0;
}

.bw-ask__text {
  font-size: var(--bw-fs-item);
  color: var(--bw-body);
  line-height: var(--bw-lh-snug);
  max-width: 34ch;
  margin: 0;
}

.bw-ask__cta { margin-top: 8px; }

/* The same ramp .bw-plan rides through the narrow three-column band (§20):
   at 761px a column is 24px of padding away from being too narrow, and a
   cell that keeps 32px while its five neighbours drop to 24px sits visibly
   out of line. --fs-plan is the token the plan NAME uses, so the title
   follows the same curve rather than being the one word that overflows. */
@media (min-width: 761px) and (max-width: 860px) {
  .bw-ask {
    padding-left: 24px;
    padding-right: 24px;
  }

  .bw-ask__title { font-size: min(25px, calc(4.55vw - 13.5px)); }
}

@media (max-width: 760px) {
  /* One column: the cell is no longer filling a hole, it is the last block
     of the list, and 559px of centred air under it is not free. */
  .bw-ask { padding: 30px 22px; }
}

/* Paper: --bw-panel is already transparent inside section 22b's print block,
   so all that is missing is the outline that tells the cell apart from the
   copy around it — exactly what .bw-plan gets three rules above. */
@media print {
  .bw-ask { border: 1px solid var(--bw-ink); }
}

/* ---- Stacked h3 + paragraph pairs ---------------------------------------
   Section 30 sizes .bw-section__h3 and .bw-section__text for ONE pair per
   column, which is what the template shows. Three pairs in a row put a 16px
   paragraph margin between a paragraph and the next heading — closer than
   the 14px inside the pair it belongs to, so the headings read as attached
   to the text above them instead of below.

   Scoped to an opt-in class rather than declared on .bw-section__text,
   because the generic pair is shared with pages other agents are writing and
   silently changing their spacing from here is not this section's business. */

.bw-stack .bw-section__text + .bw-section__h3 { margin-top: 30px; }

/* ---- Forced colours: the outlined word must actually have an outline ------
   Measured, and it is not new — index.html has it too: in forced colours
   "nejde" in the hero and "stojí" in the ceník render as a #f2ead9 stroke on
   a white Canvas, i.e. as nothing at all. Section 23 sets
   `forced-color-adjust: none` on these words on purpose, so the outline
   survives instead of being flattened into a solid word — but opting out of
   forced colours also keeps the STROKE COLOUR, and that colour is a fixed
   cream chosen for a near-black ground.

   CanvasText is the forced palette's own foreground, so this follows the
   user's high-contrast theme in both directions instead of guessing. No new
   colour enters the file; `Highlight` is already used the same way in § 23.

   All five stroke classes, not just section 30's two: it is one bug with one
   fix, and leaving the home page's two invisible while the sub-pages' three
   are legible would be a rule nobody could explain later. Additive — it
   declares a property those rules never set inside this media query. */
@media (forced-colors: active) {
  .bw-hero__stroke,
  .bw-works__stroke,
  .bw-pricing__stroke,
  .bw-page__stroke,
  .bw-section__stroke { -webkit-text-stroke-color: CanvasText; }
}

/* =========================================================================
   33. PERFORMANCE - one addition to section 30's family (performance.html)
   =========================================================================
   NUMBERING, and it needs saying: four pages were appended to this file at
   the same time and all four numbered themselves 31. This block is the third
   of them in document order, so 33 is the number a single renumbering pass
   would give it if it ordered the appended blocks by position - which is the
   pass someone still has to run. The NAME identifies this block; the digit is
   provisional.

   performance.html is the first page that argues in prose and then asks for
   something in the MIDDLE of a section rather than at the end of the page.
   Section 30 has no action row for a content section - only
   `.bw-page__actions`, which belongs to the page header - so one joins its
   family here.

   The h3-after-paragraph spacing this page also needed is deliberately NOT
   redeclared: `.bw-stack`, a few rules above, already does it, was written
   for the same reason and reached the same opt-in conclusion. A second class
   for one margin would be two names for one idea, so it is reused instead.

   New selector, opt-in, and it appears in no other document, so nothing that
   already ships changes appearance. No new colour, no new spacing unit, no
   new family: 16px is section 30's own action gap and the 480px stacking is
   the behaviour it already gives `.bw-page__actions`.
   ========================================================================= */

/* A call to action that belongs to a CONTENT section rather than to the page
   header. 26px above it, not `.bw-page__actions`' 34px: this row follows a
   paragraph carrying its own 16px bottom margin, where the header's row
   follows a lead that has none. */
.bw-section__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
  margin-top: 26px;
}

/* Below 480px the row stacks, exactly as section 30 already does for
   `.bw-page__actions` and `.bw-empty__actions`: at 320px two buttons side by
   side leave 140px of box for a 14-character Czech label. Written as its own
   rule rather than added to that selector list - section 30 is another pass's
   work and existing rules are not edited from here. */
@media (max-width: 480px) {
  .bw-section__actions { flex-direction: column; align-items: stretch; }
  .bw-section__actions > .bw-btn { text-align: center; }
}

/* =========================================================================
   34. Work / results — clip grid, category index, field lists, pull quote
   =========================================================================
   Everything the PRÁCE branch needs that the one-page site never did:
   prace.html (the index of both categories) and _pripadova-studie.html
   (the template one finished case study is copied from).

   Three constraints shaped every rule below.

   1. THE CAROUSEL IS A SINGLETON. initWorksCarousel resolves
      #bw-works-viewport / #bw-works-track with getElementById, so a document
      may hold exactly one and main.js now warns when it holds two. A work
      page is precisely where a second gallery gets pasted in, so this branch
      does not use the carousel at all: .bw-clips is a plain grid that needs
      no JavaScript, cannot be duplicated wrongly, and grows by one <article>
      per clip.

   2. IT REUSES .bw-work, IT DOES NOT REPLACE IT. The card, the 9:16 frame,
      the poster ground and the caption row are already built in sections 10
      and 26 and are already correct on cream. Only the fixed 270px width has
      to go, and it goes through `.bw-clips > .bw-work` — (0,2,0) against the
      original (0,1,0), so it wins on specificity and not merely on being
      further down the file.

   3. NO NEW COLOUR, NO NEW GROUND. Everything here is a token, which is also
      what makes the print block (§ 22b) and forced-colours correct for free:
      --bw-panel and --bw-line are already remapped there, so these blocks
      print as outlines on white without one extra rule.

   Dark ground unless stated. .bw-clips is the exception: it lives inside the
   cream .bw-works section, and every colour it touches comes from .bw-work,
   which is already the on-cream set.
   ========================================================================= */

/* ---- Page header above a diagonal ---------------------------------------
   .bw-works cuts a --bw-diag triangle out of whatever sits above it and
   pulls itself up by the same amount. --bw-diag is 3vw, so at 1920px it is
   57.6px against the page header's 68px of bottom padding — 10px of daylight
   under .bw-page__meta, and none at all on a wider screen. Add the diagonal
   back as padding and the clearance stops depending on the viewport.

   The mobile value is restated rather than inherited: § 30 sets
   `.bw-page { padding-bottom: 44px }` below 760px at the same (0,1,0) as
   this rule but EARLIER in the file, so without the second block the
   unconditional value here would silently win on a phone. */
.bw-page--diag { padding-bottom: calc(68px + var(--bw-diag)); }

/* ---- Category index -----------------------------------------------------
   Both branches of the portfolio, visible before a word of body copy — the
   structure has to read even while one half has nothing in it. The status
   line is the point of the block: it counts what is on this page, including
   the zeros, and it sits on `margin-top: auto` so the two cards agree on a
   baseline however long the description above it runs. */

.bw-cats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
  margin-top: 44px;
}

.bw-cat {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 24px 26px;
  border: var(--bw-hair) solid var(--bw-line);
  background: var(--bw-panel);
}

.bw-cat__kicker {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-3);
  color: var(--bw-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0;
}

/* A link, not a heading: the section it points at already carries the <h2>,
   and announcing the same title twice would make the page read as if it had
   two of everything. inline-flex so the 44px tap floor is met without the
   extra height falling below the text — the same fix .bw-nav__logo makes. */
.bw-cat__name {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--bw-font-display);
  font-weight: 400;
  font-size: var(--bw-fs-plan);
  line-height: var(--bw-lh-tight);
  letter-spacing: var(--bw-track-display-sm);
  text-transform: uppercase;
  color: var(--bw-cream);
  transition: color var(--bw-t-hover) ease-out;
}

/* The card's only navigation affordance. Anton caps at 25px read as a
   heading, not as a link, and colour-on-hover is no help to someone who is
   not hovering — so the jump is stated with a glyph. Down, not the ↗ the
   buttons use: this one moves within the page, it does not leave it. */
.bw-cat__arrow {
  color: var(--bw-pink);
  margin-left: 10px;
}

.bw-cat__text {
  font-size: var(--bw-fs-body-2);
  color: var(--bw-body);
  line-height: var(--bw-lh-body);
  margin: 0;
}

.bw-cat__state {
  margin: auto 0 0;
  padding-top: 8px;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-fine);
  color: var(--bw-faint-safe);
}

/* ---- Clip grid ----------------------------------------------------------
   auto-fill, not auto-fit: with three clips and room for six, auto-fit would
   stretch three cards across the full 1240px container and blow a 9:16 frame
   up to 1100px tall. auto-fill keeps the column width honest and leaves the
   empty tracks empty, which is also what makes "add a fourth card" a
   one-element edit with no CSS change. */

.bw-clips {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 34px 26px;
  align-items: start;
}

/* .bw-work is `flex: 0 0 270px; width: 270px` for the carousel track. The
   flex part is inert in a grid; the width is not, and it would pin every
   card to 270px inside a track that is already the right size. */
.bw-clips > .bw-work { width: auto; }

/* Above the intermediate band the container is at its 1240px cap and
   auto-fill answers with FOUR 293px tracks — so the three real clips left a
   293px hole on the right that reads as a broken row rather than as room to
   grow. Pinned to three, the clips fill the row at 396px each and a fourth
   one starts a second row of three. Below 1101px auto-fill already returns
   three or fewer, so nothing changes there. */
@media (min-width: 1101px) {
  .bw-clips { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* .bw-work__meta is a space-between row, which is right in a 270px carousel
   card where the two labels are short. Here both halves wrapped to two lines
   and interleaved. Stack them: the caption is the point of a portfolio card,
   the technique line is its footnote. (0,2,0) and scoped to .bw-clips, so the
   home page's carousel keeps the row it was designed with. */
.bw-clips .bw-work__meta {
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

/* The clip that links to a live post. Two separate problems, one rule:
   .bw-work__media is `overflow: hidden`, so the default +3px ring is clipped
   away entirely; and pink measures 2.96:1 on this section's cream, under the
   3:1 that WCAG 1.4.11 asks of a focus indicator. Ink is 16.22:1 — the same
   swap .bw-works__viewport:focus-visible already makes. (0,2,0) against
   `a:focus-visible` at (0,1,1), so it does not depend on source order. */
.bw-work__link:focus-visible {
  outline: var(--bw-focus);
  outline-color: var(--bw-ink);
  outline-offset: -4px;
}

/* ---- Field specification ------------------------------------------------
   The nine fields every case study has to fill. Deliberately NOT .bw-service:
   that row is --bw-fs-service (28–52px), which would shout nine times over a
   list whose whole job is to be checked against. Same language — mono index,
   hairline rows, a closing rule — one type step quieter. */

.bw-spec__item {
  display: grid;
  grid-template-columns: 64px minmax(0, 250px) minmax(0, 1fr);
  gap: 10px 24px;
  align-items: baseline;
  padding: 22px 0;
  border-top: var(--bw-hair) solid var(--bw-line);
}

.bw-spec__num {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-body-2);
  color: var(--bw-muted);
}

.bw-spec__name {
  font-weight: 700;
  font-size: var(--bw-fs-lead-3);
  color: var(--bw-cream);
  line-height: var(--bw-lh-tight);
}

.bw-spec__note {
  font-size: var(--bw-fs-body);
  color: var(--bw-body);
  line-height: var(--bw-lh-body);
}

/* The list's own closing hairline, the way .bw-services__rule closes the
   service rows: a border-bottom on :last-child would be one rule shorter and
   would also be the one line that moves when a field is added at the end. */
.bw-spec__rule { border-top: var(--bw-hair) solid var(--bw-line); }

/* ---- Fact table ---------------------------------------------------------
   The case study's nine answers. <dl>, because that is what a label/value
   pair is; the wrapping <div> per row is valid inside <dl> and is what lets
   each pair be its own grid. dd carries a 40px `margin-inline-start` from the
   UA sheet and the base reset in § 1 does not cover dl/dd, so both margins
   are zeroed here rather than left to chance. */

.bw-facts { margin: 0; }

.bw-facts__row {
  display: grid;
  grid-template-columns: minmax(0, 250px) minmax(0, 1fr);
  gap: 8px 28px;
  align-items: baseline;
  padding: 20px 0;
  border-top: var(--bw-hair) solid var(--bw-line);
}

.bw-facts__row:last-child { border-bottom: var(--bw-hair) solid var(--bw-line); }

.bw-facts__name {
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-2);
  color: var(--bw-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1.5;
}

/* 5.48:1 on this ground — the accent is legible here, unlike on cream. */
.bw-facts__num {
  color: var(--bw-pink);
  margin-right: 10px;
}

.bw-facts__value {
  margin: 0;
  font-size: var(--bw-fs-body);
  color: var(--bw-cream);
  line-height: var(--bw-lh-body);
  max-width: 62ch;
}

/* Where the number came from and for what period. It is fine print by size
   and load-bearing by content: a metric without its source is a claim.
   --bw-faint-safe, never --bw-faint — 3.32:1 fails AA on this ground. */
.bw-facts__src {
  display: block;
  margin-top: 8px;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-fine);
  line-height: 1.6;
  color: var(--bw-faint-safe);
}

/* ---- Client quote -------------------------------------------------------
   The accent rail is the device .bw-form__echo and .bw-pricing__exclusion
   already use for "this belongs to what follows". <figure> is covered by the
   base margin reset; <blockquote> and <figcaption> are not. */

.bw-quote {
  max-width: 780px;
  padding: 4px 0 4px 26px;
  border-left: 3px solid var(--bw-pink);
}

.bw-quote__text { margin: 0; }

.bw-quote__text p {
  font-size: var(--bw-fs-price);
  line-height: var(--bw-lh-snug);
  color: var(--bw-cream);
}

.bw-quote__by {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
  margin-top: 20px;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-2);
}

.bw-quote__who {
  font-weight: 700;
  color: var(--bw-cream);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.bw-quote__role { color: var(--bw-faint-safe); }

/* ---- Inline link in body copy -------------------------------------------
   `a` is `color: cream; text-decoration: none` (§ 1), so a link inside a
   paragraph is invisible until it is hovered — and colour alone would fail
   1.4.1 anyway. An explicit class rather than a `.bw-section__text a`
   descendant rule: .bw-section__text is shared multi-page furniture and a
   rule that reaches into it from here would change every other page's copy
   too. Hover is already handled by `a:not(.bw-btn):hover` in § 19. */
.bw-link {
  color: var(--bw-cream);
  text-decoration: underline;
  text-decoration-color: var(--bw-pink);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.22em;
}

/* A link that is the WHOLE value of a fact row stands alone, so WCAG 2.5.8's
   "inline in a sentence" exemption does not cover it — measured at 21px tall,
   under the 24px floor. Padding plus the matching negative margin buys 45px of
   target and moves nothing, the same trick .bw-nav__link uses for the nav. */
.bw-link--solo {
  display: inline-block;
  padding: 12px 0;
  margin: -12px 0;
}

/* ---- Small viewports ----------------------------------------------------
   Gutters come from the [data-bw-pad] list in § 21 (.bw-page, .bw-section and
   .bw-works are all in it), so only the internal grids are answered here. */

@media (max-width: 760px) {
  .bw-page--diag { padding-bottom: calc(44px + var(--bw-diag)); }

  .bw-cats { margin-top: 32px; gap: 14px; }
  .bw-cat { padding: 20px 20px; }

  .bw-clips { gap: 26px 18px; }

  /* Two columns instead of three: the note drops under the name rather than
     beside it, and `grid-column: 2` is what stops it flowing back under the
     index number in column 1. */
  .bw-spec__item {
    grid-template-columns: 52px minmax(0, 1fr);
    gap: 6px 16px;
    padding: 18px 0;
  }

  .bw-spec__note { grid-column: 2; }

  .bw-facts__row {
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
    padding: 18px 0;
  }

  .bw-quote { padding-left: 18px; }
}

@media (max-width: 640px) {
  .bw-cats { grid-template-columns: minmax(0, 1fr); }
}

/* =========================================================================
   35. Language switch — the header's CS / EN control
   -------------------------------------------------------------------------
   The site ships in two complete languages, Czech and English, and this is
   how a visitor moves between them. Three decisions worth writing down.

   1. NO FLAG. A flag is a state, not a language: Czech is spoken outside
      Czechia, English is not owned by any one country, and a 16px flag is
      illegible at the sizes this header has room for. Two two-letter codes
      are what the control is, and what it says.

   2. IT IS NOT A NAV LINK. Sized off --bw-fs-mono-4 (12px) against the nav's
      14px, and separated from the menu by nothing but a 7px gap, so it reads
      as a control sitting beside the navigation rather than a seventh
      destination inside it. The current language is cream with the same pink
      underline the nav uses for aria-current="page" — one you-are-here
      vocabulary for the whole header, not two.

   3. THE HIT AREA IS BOUGHT WITH PADDING AGAINST AN EQUAL NEGATIVE MARGIN,
      the trick .bw-nav__link already uses: 44px of target, zero change to the
      layout width. It is applied to BOTH options, not just the live one, so
      the row does not resize on the day the disabled EN becomes a real <a>.

   WIDTH. The nav row was measured to 3.7px of slack at 761px and 8.1px at
   320px before this control existed, so it could not simply be added — see
   sections 20 and 21, where the mobile ladder was re-measured around it.
   Renaming PERFORMANCE (94.3px) to VÝKON (42.8px) paid for most of it.
   ========================================================================= */

.bw-nav__item--lang { display: flex; }

.bw-lang {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--bw-font-mono);
  font-size: var(--bw-fs-mono-4);
  line-height: 1.2;
  letter-spacing: 0.5px;
}

.bw-lang__opt {
  display: inline-block;
  color: var(--bw-body);
  padding: 13px 3px;          /* a11y: 44px tall hit area, layout unchanged */
  margin: -13px -3px;
  transition: color var(--bw-t-hover) ease-out;
}

/* Current language. Same cue as .bw-nav__link[aria-current="page"], one size
   down: colour would be the only signal otherwise, and colour alone is not a
   signal for everyone. */
.bw-lang__opt--on {
  color: var(--bw-cream);
  text-decoration: underline;
  text-decoration-color: var(--bw-pink);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.3em;
}

/* The other language, while /en/ is still being built. It is a <span>, not an
   <a>: there is no href to follow, so there is no 404 to land on. The dotted
   underline is the browser's own convention for "this has a tooltip", and the
   title attribute plus a .bw-sr-only sentence say what the tooltip says. */
.bw-lang__opt--soon {
  /* --bw-muted, not an opacity fade: a faded label is a contrast failure
     dressed up as a state. 5.53:1 on the page ground, and the dotted rule
     plus cursor: default carry "not yet" without dimming the text. */
  color: var(--bw-muted);
  cursor: default;
  text-decoration: underline dotted;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.3em;
}

/* =========================================================================
   36. English chrome — the nav ladder for /en/
   -------------------------------------------------------------------------
   Added with the English mirror. APPEND-ONLY and scoped to html[lang="en"]:
   nothing here can reach a Czech page, and every selector is one class
   heavier than the rule it answers, so it wins inside the same media band
   without that rule being touched.

   WHY IT IS NEEDED AT ALL. Section 21's ladder was measured against the
   Czech words. The English ones are longer, and the header is the one row on
   this site that has no slack to lend:

     SLUŽBY -> SERVICES      +2 characters
     VÝKON  -> PERFORMANCE   +6      <- this is the whole problem
     PRÁCE  -> WORK          -1
     CENÍK  -> PRICING       +2
     KONTAKT/O NÁS           unchanged
     CHCI NABÍDKU -> GET A QUOTE  -1

   Measured on the live page: the full English menu is 68px wider than the
   Czech one, and the phone row (PRICING + switch + CTA) is 9.5px wider. The
   Czech ladder ships with 21.7px of slack at 761px and 9.7px at 360px, so
   both deltas land the English header in overflow. Unpatched it wrapped to
   two rows (121.1px tall) everywhere from 808px down to 761px, and sat at
   0.2px of slack at 360px — a number that is not a margin, it is a rounding
   error waiting for a font swap.

   Three rules, each measured, none of them clever.
   ========================================================================= */

/* ---- 1. The desktop band keeps every destination, on a tighter gap -------
   820-1000px carries all six links plus the switch plus the CTA. Section 20
   already tightens the menu gap to 13px here for Czech; English needs 10px,
   which buys 7 x 3 = 21px and puts 33.2px of slack at 820. That is what
   keeps iPad portrait (820px Air, 834px Pro 11") on the full menu instead of
   the phone header — the widths where a visitor is reading, not tapping. */
@media (min-width: 820px) and (max-width: 1000px) {
  html[lang="en"] .bw-nav__menu { gap: 10px; }
}

/* ---- 2. --hide-sm starts 59px earlier ------------------------------------
   The row runs out at 808px in English against 760px in Czech, so the five
   secondary links step out at 819px instead. This is not a new mechanism: it
   is section 20's own answer, applied where English actually needs it. What
   is dropped is dropped into the footer sitemap, which is on every page for
   exactly this reason and which the English footer carries verbatim.

   Nothing else was on the table. Gap-tightening alone cannot close a 46.8px
   deficit at 761px (seven gaps would have to go to 6px, which reads as one
   run-on word), and shrinking the nav type for one 59px band would make the
   two languages look like different sites at the same window width. */
@media (max-width: 819px) {
  html[lang="en"] .bw-nav__item--hide-sm { display: none; }
}

/* ---- 3. The phone bands give back the 9.5px PRICING costs ----------------
   PRICING is 17px wider than CENÍK and GET A QUOTE is 7.5px narrower than
   CHCI NABÍDKU, so every phone width below 400px is 9.5px tighter than the
   number section 21 signed off on. Rather than drop PRICING — the money page,
   and the one section 21 step 4 went out of its way to keep at 360px — the
   width comes back out of two gaps and the CTA's side padding, 2px each.

   Measured after this rule, English against the Czech numbers in section 21:
     400px  26.2   (CZ 35.7)      375px  25.2   (CZ 24.7)
     390px  36.2   (CZ 25.7)      368px  18.2   (CZ 17.7)
     380px  26.2                  360px  10.2   (CZ  9.7)
   One row at every width from 320 to 1920, zero horizontal overflow, and the
   narrowest margin on the whole sweep is that 10.2px at 360px. Below 360px
   section 21 step 4 takes over unchanged: PRICING steps out, the switch and
   the CTA stay, and 320px keeps 36.2px. */
@media (max-width: 399px) {
  html[lang="en"] .bw-nav { gap: 6px; }
  html[lang="en"] .bw-nav__menu { gap: 6px; }

  html[lang="en"] .bw-nav__cta {
    padding-left: 8px;
    padding-right: 8px;
  }
}

/* =========================================================================
   37. English hero — the one word that does not fit
   -------------------------------------------------------------------------
   Added with the English mirror, APPEND-ONLY and scoped to html[lang="en"].
   It answers exactly one measured failure and nothing else.

   THE FAILURE. The English home page leads on the positioning the owner wrote
   himself, "Growth through Creative + Performance", and the last word of it is
   eleven characters of 64px Anton — 341.6px, or 5.337em. The hero's content
   box is the viewport minus the 20px mobile gutters on both sides, so the word
   stops fitting at 382px and below. It did not overflow the page: section 1's
   `overflow-wrap: break-word` caught it, which is the fail-safe doing its job.
   What it produced instead was PERFORMA / NCE — a hero headline broken mid-word
   without a hyphen, on the iPhone SE (375px) and every 360px Galaxy. Verified
   in a screenshot before and after; the unfixed hero was also 75px TALLER at
   375px, because the broken word cost the title a fifth line.

   Czech does not have this problem and must not pay for it. The longest word
   in the Czech hero is "přeskočit." at 10 characters, which fits at 320px with
   room to spare, so this whole section hangs off html[lang="en"].

   WHY A RAMP AND NOT A REWRITE. The line is the owner's own English, quoted;
   shortening it to fit a 360px phone is not a decision a stylesheet gets to
   make. So the type ramps down instead, and only where it has to: 382px is the
   last width where 64px fits, so the rule starts at 381px and is inert above
   it. At 375px the hero reads 62.7px, at 360px 59.8px, at 320px 52.4px, and
   the title holds FOUR lines all the way down instead of five.

   WHY cqw AND NOT vw. The constraint is "fit inside this box", and 100vw is
   not that box: it includes the scrollbar. Section 20 has already been bitten
   by exactly this gap between window width and layout width — 846px was too
   low there for the same reason — and here the error is worst precisely where
   the margin is thinnest. A container query unit measures the container, so
   the fit is exact with a classic scrollbar and with an overlay one. The vw
   line above it is the fallback for a browser without container units: it
   subtracts 48px rather than 40px to buy back the scrollbar it cannot see.
   Both are capped by min() against --bw-fs-hero, so neither can ever make the
   hero BIGGER than the token says. The token itself is untouched.

   18.7cqw is 100 / 5.337, i.e. the font size at which the word is exactly as
   wide as the container. If the hero copy ever changes, re-measure it: the
   number is a property of that word in that face, not a taste.
   ========================================================================= */

@media (max-width: 381px) {
  html[lang="en"] .bw-hero__inner { container-type: inline-size; }

  html[lang="en"] .bw-hero__title {
    font-size: min(var(--bw-fs-hero), calc((100vw - 48px) / 5.34));
    font-size: min(var(--bw-fs-hero), 18.7cqw);
  }
}

/* =========================================================================
   31. Loader — localhost only, created by main.js
   =========================================================================
   Deliberately almost nothing: the page's own ground, the brand's own mark,
   one fade out. At this size a spinner would be a second brand.

   There is no rule here that hides page content. The cover sits above it and
   then leaves; if this stylesheet loaded and main.js did not, none of these
   selectors ever match, because the element they describe is never created.
   That asymmetry is the point — the failure mode is "no loader", never
   "no page". */

.bw-loader {
  position: fixed;
  inset: 0;
  z-index: 999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bw-bg);
  opacity: 1;
  transition: opacity 0.38s var(--bw-ease);
}

.bw-loader--out {
  opacity: 0;
  pointer-events: none;
}

.bw-loader__mark {
  font-family: var(--bw-font-display);
  font-size: clamp(56px, 9vw, 104px);
  line-height: 1;
  color: var(--bw-pink);
  /* The same constant the footer sign-off turns at, so the mark behaves the
     same way in both places it appears — just faster, because this one has
     about a second to read as alive rather than a whole page. */
  animation: bw-signoff-spin 2.6s linear infinite;
  transform-origin: 0.226em 0.24em;
}

/* The scrollbar would otherwise appear and disappear behind the cover, which
   reads as the page twitching the moment it is revealed. */
.bw-loading,
.bw-loading body {
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .bw-loader { transition: none; }
  .bw-loader__mark { animation: none; }
}

/* =========================================================================
   32. Cookie consent
   =========================================================================
   Both buttons are the same size and sit side by side. That is not a style
   choice: UOOU's guidance is that refusing must be as easy as agreeing, and
   the usual pattern — a bright ACCEPT next to a grey "Nastavení" that hides
   the refusal two clicks deep — is the thing it was written against.

   The bar sits at the bottom and does not cover the page: no backdrop, no
   focus trap, no scroll lock. A visitor who wants to read first can read
   first. */

.bw-consent {
  position: fixed;
  left: max(16px, env(safe-area-inset-left, 0px));
  right: max(16px, env(safe-area-inset-right, 0px));
  bottom: max(16px, env(safe-area-inset-bottom, 0px));
  z-index: 200;                       /* above the sticky CTA, which is 60 */
  max-width: 720px;
  margin: 0 auto;
  padding: 20px 22px;
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
  background: var(--bw-panel);
  border: var(--bw-rule) solid var(--bw-line);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.3s var(--bw-ease), transform 0.3s var(--bw-ease);
}

.bw-consent--in {
  opacity: 1;
  transform: translateY(0);
}

.bw-consent__text {
  flex: 1 1 300px;
  margin: 0;
  font-size: var(--bw-fs-fine);
  line-height: 1.6;
  color: var(--bw-body);
}

.bw-consent__link {
  color: var(--bw-cream);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.bw-consent__link:hover,
.bw-consent__link:focus-visible { color: var(--bw-pink); }

.bw-consent__actions {
  display: flex;
  gap: 10px;
  flex: 0 0 auto;
}

.bw-consent__btn {
  min-height: 44px;
  padding: 12px 22px;
  font-size: var(--bw-fs-mono-3);
}

/* Same footprint as the accept button, deliberately. It is outlined rather
   than filled because one of the two has to be the default action, but it is
   not smaller, not greyed out and not hidden behind another screen. */
.bw-consent__btn--deny {
  border: var(--bw-rule) solid var(--bw-stroke);
  color: var(--bw-cream);
  background: none;
}

.bw-consent__btn--deny:hover,
.bw-consent__btn--deny:focus-visible {
  border-color: var(--bw-pink);
  color: var(--bw-pink);
}

@media (max-width: 560px) {
  .bw-consent { padding: 18px; gap: 14px; }
  .bw-consent__actions { width: 100%; }
  .bw-consent__btn { flex: 1 1 0; }
}

@media (prefers-reduced-motion: reduce) {
  .bw-consent { transition: none; opacity: 1; transform: none; }
}

/* The consent control is a <button>, because it opens a dialog rather than
   navigating — but it sits in a list of links and must not look like a
   stranger there. */
.bw-footer__consent {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}
