/* ============================================================================
   chciweb.online — site stylesheet
   Reads tokens.css. NO hex, NO font stack, NO spacing number appears below this
   line — if you need a value that isn't a token, add the token (INV-ONE-HOME).
   Hand-written, no build step: what we write is what ships, which is what makes
   the zero-errors requirement (C-23) cheap to hold.
   ============================================================================ */

@font-face{font-family:'Fraunces';font-style:normal;font-weight:400 700;font-display:swap;
  src:url("../fonts/fraunces-1.118070a4b0cf.woff2") format('woff2');
  unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB;}
@font-face{font-family:'Fraunces';font-style:normal;font-weight:400 700;font-display:swap;
  src:url("../fonts/fraunces-2.a04d353989ad.woff2") format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'Fraunces';font-style:normal;font-weight:400 700;font-display:swap;
  src:url("../fonts/fraunces-3.069cb90e11a3.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;}

/* ── Reset ─────────────────────────────────────────────────────────────────── */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{margin:0;background:var(--bg);color:var(--text);
  font-family:var(--font-body);font-size:var(--step-0);line-height:var(--leading-body);
  -webkit-font-smoothing:antialiased}
img,svg{max-width:100%;height:auto;display:block}
ul,ol{margin:0;padding:0;list-style:none}

h1,h2,h3{font-family:var(--font-display);font-weight:600;line-height:var(--leading-tight);
  margin:0;letter-spacing:-0.02em;text-wrap:balance}
p{margin:0}

a{color:var(--link);text-underline-offset:0.18em;text-decoration-thickness:0.08em}
a:hover{color:var(--link-hover)}

/* Focus is restyled, never removed (WCAG 2.4.7 / 2.4.13). */
:focus-visible{outline:3px solid var(--focus);outline-offset:3px;border-radius:var(--radius-s)}

.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}

/* Skip link (WCAG 2.4.1) — the first thing a keyboard user meets. */
.skip{position:absolute;left:var(--space-s);top:calc(var(--space-s) * -4);z-index:10;
  background:var(--brand);color:var(--paper);padding:var(--space-2xs) var(--space-s);
  border-radius:var(--radius-m);text-decoration:none;font-weight:600;
  transition:top var(--dur-fast) var(--ease-out)}
.skip:focus{top:var(--space-s);color:var(--paper)}
.skip{display:inline-flex;align-items:center;min-height:2.75rem}

.wrap{max-width:var(--content-max);margin-inline:auto;padding-inline:var(--gutter)}

/* ── Wordmark ──────────────────────────────────────────────────────────────── */
.logo{display:inline-flex;align-items:center;gap:0.02em;text-decoration:none;
  font-family:var(--font-display);font-weight:600;letter-spacing:-0.035em;
  font-size:var(--step-1);color:var(--ink)}
.logo{min-height:2.75rem}
.logo:hover{color:var(--ink)}
.logo--sm{font-size:var(--step-0)}
.logo__beam{width:0.085em;height:0.8em;background:var(--brand);border-radius:0.05em;
  margin-left:0.14em;flex:none}

/* ── Header ────────────────────────────────────────────────────────────────── */
/* !! ANYTHING JS SCROLLS TO MUST CLEAR THE STICKY HEADER, or it lands BEHIND it. `F-113`.
 *
 * `smartform.js` advances a step with `scrollIntoView({block:"start"})` on the step's legend, which
 * puts that element at the very top of the viewport -- underneath `.site-header`, which is
 * `position:sticky; top:0`. Measured 2026-08-18: stepping into step 2 put its first field at
 * `top=61` on a 390px phone and `top=65` on desktop, against a header whose bottom edge is at `69`.
 * The customer was asked to fill a field that was **partly hidden by the header**, on the very step
 * where they type their name and company.
 *
 * `scroll-margin-top` is the mechanism `scrollIntoView` itself honours, which is why the fix belongs
 * here rather than in the JS: no scroll call has to remember to subtract anything.
 *
 * 5rem clears the measured 69px with room to spare. It is a MEASURED constant, not a guess -- and
 * `tests/test_layout_geometry.py::test_stepping_never_leaves_the_first_field_UNDER_the_header`
 * fails if the header ever grows past it, which is what stops this number going quietly stale.
 */
.formstep, .formstep legend, .formstep .fieldset__legend, .form { scroll-margin-top: 5rem }

.site-header{position:sticky;top:0;z-index:5;
  background:color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter:blur(10px);border-bottom:var(--border)}
.site-header__inner{display:flex;align-items:center;justify-content:space-between;
  gap:var(--space-m);padding-block:var(--space-xs)}
.nav{display:flex;align-items:center;gap:var(--space-m);flex-wrap:wrap;
  font-size:var(--step--1)}
/* 🚨 44px MINIMUM TAP TARGET (WCAG 2.5.5 / Apple HIG). Measured 2026-08-05, every nav link was
   15px tall on a phone — hittable only if you aim. The height comes from padding rather than a
   fixed box so the text stays on the baseline the design intends, and `align-items:center` on
   `.nav` keeps the row visually tight despite the bigger hit area. */
.nav a{color:var(--text);text-decoration:none;
  display:inline-flex;align-items:center;min-height:2.75rem;padding-inline:0.15rem}
.nav--stack a{min-height:2.75rem}   /* footers get mis-tapped more, not less */
.nav a:hover{color:var(--brand)}
.nav--stack{flex-direction:column;align-items:flex-start;gap:var(--space-2xs)}
@media (max-width:46rem){
  /* 🚨 THE STICKY HEADER IS THE MOST EXPENSIVE 100px ON THE SITE — it is not spent once, it is
     spent on every screen the customer scrolls past. Wrapping the three section links onto a
     second row cost ~50px permanently, and giving them proper 44px tap targets made that worse.

     So on a phone the sticky bar carries the two things that must always be reachable: who we are,
     and the way to order. The section links stay in the footer and the page is a single scroll
     anyway — a `#cenik` anchor is worth far less on a 390px screen than the 50px it occupies on
     every one of them. */
  .site-header__inner{flex-wrap:nowrap;gap:var(--space-s)}
  .nav > li:not(:last-child){display:none}
  .nav{gap:var(--space-s)}
}

/* ── Buttons ───────────────────────────────────────────────────────────────── */
.btn{display:inline-flex;align-items:center;gap:var(--space-2xs);min-height:2.75rem;    /* WCAG 2.5.5 — btn--sm was 40px on a phone */
  
  font:inherit;font-weight:600;text-decoration:none;cursor:pointer;
  padding:0.8em 1.5em;border-radius:var(--radius-xl);border:2px solid transparent;
  transition:transform var(--dur-fast) var(--ease-out),
             background-color var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out)}
.btn--sm{padding:0.5em 1em;font-size:var(--step--1)}
.btn--lg{padding:1em 2em;font-size:var(--step-1)}
.btn--primary{background:var(--brand);color:var(--paper)}
.btn--primary:hover{background:var(--terracotta-deep);color:var(--paper);transform:translateY(-1px)}
.btn--ghost{border-color:var(--rule);color:var(--text)}
.btn--ghost:hover{border-color:var(--brand);color:var(--brand)}
.btn__arrow{transition:transform var(--dur-base) var(--ease-out)}
.btn:hover .btn__arrow,.card__cta:hover .btn__arrow{transform:translateX(0.25em)}

/* ── Sections ──────────────────────────────────────────────────────────────── */
.section{padding-block:var(--space-2xl)}
.section--raised{background:var(--bg-raised);border-block:var(--border)}
.section--close{text-align:center;padding-block:var(--space-3xl)}
.section h2{font-size:var(--step-3);margin-bottom:var(--space-s)}
.section__lede{font-size:var(--step-1);color:var(--text-muted);max-width:52ch;
  margin-bottom:var(--space-xl)}
.section--close .section__lede{margin-inline:auto;margin-bottom:var(--space-l)}
.eyebrow{font-family:var(--font-mono);font-size:var(--step--1);letter-spacing:0.08em;
  text-transform:uppercase;color:var(--text-muted);margin-bottom:var(--space-2xs)}
.close__title{font-size:var(--step-4);margin-bottom:var(--space-s)}

/* ── Hero ──────────────────────────────────────────────────────────────────── */
.hero{padding-block:var(--space-3xl) var(--space-2xl)}
.hero__title{font-size:var(--step-5);letter-spacing:-0.035em}
/* `F-134` — the hero's inner block measured **845px on a 390x844 phone**: the visitor's entire first
   screen was hero and nothing else, with the price list starting below the fold. Trimmed by tightening
   the rhythm around the lede rather than shrinking the type, because the headline is the one thing on
   this page that should be big. */
.hero__lede{font-size:var(--step-1);color:var(--text-muted);max-width:48ch;
  margin-block:var(--space-s) var(--space-m)}
.hero__cta{display:flex;flex-wrap:wrap;gap:var(--space-s);align-items:center}

/* `--space-2xl` above a bordered list inside the hero was ~48px of gap doing the job a 1px rule was
   already doing. The rule separates; the gap only pushed the fold down. */
.included{display:flex;flex-wrap:wrap;gap:var(--space-2xs) var(--space-l);
  margin-top:var(--space-m);padding-top:var(--space-s);border-top:var(--border)}
.included li{display:flex;flex-direction:column;gap:0.1em;max-width:22ch}
.included__label{display:flex;align-items:center;gap:var(--space-2xs);font-weight:600;
  font-size:var(--step--1)}
.included__label::before{content:"";width:0.45em;height:0.45em;border-radius:50%;
  background:var(--teal);flex:none}
.included__detail{color:var(--text-muted);font-size:var(--step--1);padding-left:1.1em}

/* ── Cards / configurator ──────────────────────────────────────────────────── */
.cards{display:grid;gap:var(--space-m);
  grid-template-columns:repeat(auto-fit,minmax(16rem,1fr))}
/* `F-144` — the WHOLE card is the click target, and it says so on hover.
   Maker: *"All our cenik options should be clickable anywhere in the component and should have mouse over
   slight background change ... so customer knows he's selecting it UX wise."* Before this, only the small
   `Vybrat →` link was clickable: the card lifted on hover, which PROMISES clickability, and then a click
   two centimetres away did nothing. A hover state that lies is worse than none. */
.card{position:relative;display:flex;flex-direction:column;gap:var(--space-2xs);
  background:var(--paper);border:2px solid var(--rule);border-radius:var(--radius-l);
  padding:var(--space-m);cursor:pointer;
  transition:border-color var(--dur-fast) var(--ease-out),
             background-color var(--dur-fast) var(--ease-out),
             transform var(--dur-fast) var(--ease-out),
             box-shadow var(--dur-fast) var(--ease-out)}
.card:hover,.card:focus-within{border-color:var(--brand);background:var(--sand);transform:translateY(-3px);
  box-shadow:var(--shadow-m)}
.card--soft{border-style:dashed}
.card__name{font-size:var(--step-1)}
.card__price{font-family:var(--font-display);font-size:var(--step-2);color:var(--brand);
  line-height:1.15}
.card__price--none{color:var(--text-muted)}
.card__tagline{font-weight:600;font-size:var(--step--1)}
.card__desc{color:var(--text-muted);font-size:var(--step--1);flex:1}
.card__cta{display:inline-flex;align-items:center;gap:var(--space-2xs);
  min-height:2.75rem;                    /* WCAG 2.5.5 — this is a BUY button, measured 22px */
  margin-top:var(--space-2xs);font-weight:600;font-size:var(--step--1);
  color:var(--link);text-decoration:none}
.card__cta:hover{color:var(--link-hover)}
/* THE STRETCHED LINK (`F-144`). One pseudo-element covers the card, so the existing `<a>` owns the whole
   surface. Deliberately NOT a second link, an `onclick`, or a wrapping `<a>`:
     · a second link means two tab stops and a screen reader announcing the same destination twice;
     · an `onclick` on the card is not a link — no middle-click, no ⌘-click, no "open in new tab", and
       nothing for a keyboard;
     · wrapping the `<h3>` and the price in an anchor makes the accessible name the entire card text.
   The trade-off is real and accepted: text inside the card is no longer selectable, because the overlay
   sits above it. For a card whose whole purpose is "click to choose this", that is the right side of the
   trade — and `.card__cta` keeps its own visible focus ring, so keyboard users still see where they are. */
.card__cta::after{content:"";position:absolute;inset:0;border-radius:inherit;z-index:1}

/* ── Addons ────────────────────────────────────────────────────────────────── */
.addons__title{font-size:var(--step-1);margin-top:var(--space-2xl);
  margin-bottom:var(--space-s)}
.addons{display:grid;gap:var(--space-s);grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.addon{display:grid;gap:0.1em;padding:var(--space-s) var(--space-m);
  background:var(--paper);border-radius:var(--radius-m);border:var(--border)}
.addon__name{font-weight:600}
.addon__price{font-family:var(--font-display);font-size:var(--step-1);color:var(--brand)}
.addon__tagline{color:var(--text-muted);font-size:var(--step--1)}

/* ── Steps ─────────────────────────────────────────────────────────────────── */
.steps{display:grid;gap:var(--space-m);
  grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));counter-reset:step}
/* `F-124` — the five steps must sit on ONE line, which Maker asked for by name.
   `[V]` before: `auto-fit` + a 15rem floor yielded **4 columns** in the 1104px wrap at 1280px, so
   step 5 dropped to a second row on its own — a five-step promise ("Pět kroků") rendered as 4+1.
   `grid-auto-flow:column` is deliberately count-AGNOSTIC: it does not encode "5" anywhere, so
   adding a sixth step keeps the row intact instead of silently re-wrapping. `grid-template-columns`
   must be cleared or the explicit track list wins and the flow change does nothing. */
@media (min-width:58rem){
  .steps{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:1fr}
}
.step{position:relative;padding-top:var(--space-m);border-top:3px solid var(--rule);
  transition:border-color var(--dur-base) var(--ease-out)}
.step:hover{border-top-color:var(--brand)}
.step__num{font-family:var(--font-display);font-size:var(--step-2);color:var(--brand);
  display:block;line-height:1;margin-bottom:var(--space-2xs)}
.step__title{font-size:var(--step-0);margin-bottom:var(--space-3xs)}
.step p{color:var(--text-muted);font-size:var(--step--1)}

/* ── Notes ─────────────────────────────────────────────────────────────────── */
.note{background:var(--terracotta-wash);border-left:3px solid var(--brand);
  padding:var(--space-s) var(--space-m);border-radius:0 var(--radius-m) var(--radius-m) 0;
  font-size:var(--step--1);max-width:var(--measure)}
.note--inline{margin-top:var(--space-m)}

/* ── Footer ────────────────────────────────────────────────────────────────── */
.site-footer{background:var(--ink);color:var(--paper);padding-block:var(--space-xl)}
.site-footer a{color:var(--paper)}
.site-footer a:hover{color:var(--sand)}
.site-footer .logo{color:var(--paper)}
.site-footer .logo__beam{background:var(--paper)}
.site-footer__grid{display:grid;gap:var(--space-l);
  grid-template-columns:repeat(auto-fit,minmax(14rem,1fr))}
.site-footer__line{color:var(--sand-deep);font-size:var(--step--1);margin-top:var(--space-2xs)}
.site-footer__legal{margin-top:var(--space-xl);padding-top:var(--space-s);
  border-top:1px solid color-mix(in srgb, var(--paper) 18%, transparent);
  color:var(--sand-deep);font-size:var(--step--1)}

/* ── Forms (SPEC §5 intake, §13 permalink) ─────────────────────────────────── */
.wrap--narrow{max-width:46rem}
.form__title{font-size:var(--step-4);margin-bottom:var(--space-s)}

.fieldset{border:0;padding:0;margin:0 0 var(--space-xl)}
.fieldset__legend{font-family:var(--font-display);font-size:var(--step-1);font-weight:600;
  padding:0;margin-bottom:var(--space-s)}

.field{margin-bottom:var(--space-m)}
.field label{display:block;font-weight:600;font-size:var(--step--1);margin-bottom:var(--space-3xs)}
.field__row{display:flex;gap:var(--space-2xs);align-items:stretch;flex-wrap:wrap}
.field__row input{flex:1 1 12rem}
.field__help{color:var(--text-muted);font-size:var(--step--1);margin-top:var(--space-3xs)}
.field__error{color:var(--terracotta-deep);font-size:var(--step--1);font-weight:600;
  margin-top:var(--space-3xs)}
.req{color:var(--brand)}

input[type=text],input[type=email],input[type=tel],input[type=url],input[type=number],textarea{
  width:100%;font:inherit;color:var(--text);background:var(--paper);
  border:2px solid var(--rule);border-radius:var(--radius-m);
  padding:0.6em 0.8em;
  transition:border-color var(--dur-fast) var(--ease-out)}
input:focus,textarea:focus{border-color:var(--brand);outline-offset:1px}
textarea{resize:vertical}

.choices{display:grid;gap:var(--space-2xs)}
.choices--inline{grid-auto-flow:column;grid-auto-columns:max-content;gap:var(--space-m)}
/* `min-height:44px` — these are the primary tap targets of the whole order form, and the `.field`
 * wrapper added for `F-119`'s error slot cost them 1px (43px, measured). Same rule as `.tab`
 * (`F-115`): a control a thumb must hit clears the published minimum, and the geometry sweep is what
 * noticed the regression the same minute it was introduced. */
.choice{min-height:44px;display:flex;align-items:flex-start;gap:var(--space-2xs);cursor:pointer;
  padding:var(--space-2xs) var(--space-xs);border:2px solid var(--rule);
  border-radius:var(--radius-m);font-weight:500;
  transition:border-color var(--dur-fast) var(--ease-out),
             background-color var(--dur-fast) var(--ease-out)}
.choice:hover{border-color:var(--brand)}
.choice:has(input:checked){border-color:var(--brand);background:var(--terracotta-wash)}
.choice input{margin-top:0.25em;accent-color:var(--brand);flex:none}
@media (max-width:32rem){.choices--inline{grid-auto-flow:row;gap:var(--space-2xs)}}

.consent{display:flex;align-items:flex-start;gap:var(--space-2xs);cursor:pointer;
  margin-bottom:var(--space-s);font-size:var(--step--1)}
.consent input{margin-top:0.25em;accent-color:var(--brand);flex:none;width:1.1em;height:1.1em}
/* The §1837 consent is visually distinct on purpose: it is a separate, informed act, and it must
   not read as fine print attached to the terms checkbox above it. */
.consent--withdrawal{background:var(--terracotta-wash);border-left:3px solid var(--brand);
  padding:var(--space-s) var(--space-m);border-radius:0 var(--radius-m) var(--radius-m) 0}

/* Honeypot: off-screen rather than display:none, so a bot checking visibility still fills it. */
.honeypot{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

.alert{background:var(--terracotta-wash);border-left:3px solid var(--brand);
  padding:var(--space-s) var(--space-m);border-radius:0 var(--radius-m) var(--radius-m) 0;
  margin-bottom:var(--space-m)}

/* ── ARES result ───────────────────────────────────────────────────────────── */
.ares{margin-top:var(--space-2xs);padding:var(--space-s) var(--space-m);
  background:var(--bg-raised);border-radius:var(--radius-m);border-left:3px solid var(--teal)}
.ares__name{font-weight:600}
.ares__addr{font-size:var(--step--1)}
.ares__meta{font-size:var(--step--1);color:var(--text-muted)}

/* ── Order permalink ───────────────────────────────────────────────────────── */
.status{display:inline-flex;flex-direction:column;gap:0.1em;
  background:var(--bg-raised);border-radius:var(--radius-m);
  padding:var(--space-s) var(--space-m);margin-bottom:var(--space-l);
  border-left:3px solid var(--brand)}
.status__label{font-size:var(--step--1);color:var(--text-muted);text-transform:uppercase;
  letter-spacing:0.06em;font-family:var(--font-mono)}
.status__value{font-family:var(--font-display);font-size:var(--step-1);font-weight:600}

.summary{display:grid;grid-template-columns:max-content 1fr;gap:var(--space-2xs) var(--space-m);
  margin:0 0 var(--space-l)}
.summary dt{font-weight:600;font-size:var(--step--1);color:var(--text-muted)}
.summary dd{margin:0}
.summary__note{color:var(--text-muted);font-size:var(--step--1)}
@media (max-width:32rem){.summary{grid-template-columns:1fr;gap:0}
  .summary dd{margin-bottom:var(--space-2xs)}}

.timeline__title{font-size:var(--step-1);margin:var(--space-xl) 0 var(--space-s)}
.timeline{display:grid;gap:var(--space-2xs);border-left:2px solid var(--rule);
  padding-left:var(--space-m)}
.timeline__item{display:flex;flex-wrap:wrap;gap:var(--space-2xs) var(--space-s);
  font-size:var(--step--1)}
.timeline__item time{color:var(--text-muted);font-family:var(--font-mono);flex:0 0 10rem}

/* ── Permalink actions + thread (SPEC §13) ─────────────────────────────────── */
.actions{background:var(--bg-raised);border-radius:var(--radius-l);
  padding:var(--space-m);margin-bottom:var(--space-l);border-left:3px solid var(--brand)}
.actions__title{font-size:var(--step-1);margin-bottom:var(--space-s)}
.actions__form{margin-bottom:var(--space-m)}
.actions__form:last-child{margin-bottom:0}
.actions__confirm{font-size:var(--step--1);color:var(--text-muted);margin-bottom:var(--space-2xs)}

.alert--error{border-left-color:var(--terracotta-deep)}

.thread{display:grid;gap:var(--space-s);margin-bottom:var(--space-m)}
.msg{background:var(--bg-raised);border-radius:var(--radius-m);padding:var(--space-s) var(--space-m)}
/* The customer's own messages are visually distinct so the thread reads as a conversation
   rather than a log — at a glance you can see who said what. */
.msg--customer{background:var(--terracotta-wash);margin-left:var(--space-l)}
.msg__meta{display:flex;gap:var(--space-s);align-items:baseline;
  font-size:var(--step--1);color:var(--text-muted);margin-bottom:var(--space-3xs)}
.msg__author{font-weight:600;color:var(--text)}
.msg__meta time{font-family:var(--font-mono)}
.msg__body{white-space:pre-wrap}
.msg-form{margin-bottom:var(--space-l)}
@media (max-width:32rem){.msg--customer{margin-left:var(--space-s)}}

/* ── Invoice on the permalink (SPEC §9.2) ──────────────────────────────────── */
.invoice{background:var(--bg-raised);border-radius:var(--radius-l);padding:var(--space-m);
  margin-bottom:var(--space-l);border-left:3px solid var(--teal)}
.invoice__title{font-size:var(--step-1);margin-bottom:var(--space-s)}
.invoice__total{font-family:var(--font-display);font-size:var(--step-2);color:var(--brand-on-raised)}
.invoice__pay{display:flex;gap:var(--space-l);flex-wrap:wrap;align-items:flex-start;
  margin-bottom:var(--space-m)}
.invoice__label{font-size:var(--step--1);color:var(--text-muted);margin-top:var(--space-2xs)}
.invoice__iban{font-family:var(--font-mono);font-size:var(--step--1)}
.invoice__qr{margin:0}
.invoice__qr img{border-radius:var(--radius-m)}
.invoice__qr figcaption{font-size:var(--step--1);color:var(--text-muted);margin-top:var(--space-3xs)}

/* ── Discount code + reviews (SPEC §11, §12) ───────────────────────────────── */
.discount{background:var(--terracotta-wash);border-radius:var(--radius-l);
  padding:var(--space-m);margin-bottom:var(--space-l);border-left:3px solid var(--brand)}
.discount__title{font-size:var(--step-1);margin-bottom:var(--space-2xs)}
.discount__code{font-family:var(--font-mono);font-size:var(--step-2);font-weight:600;
  letter-spacing:0.06em;color:var(--brand-on-raised);user-select:all}

.review-invite{background:var(--bg-raised);border-radius:var(--radius-l);
  padding:var(--space-m);margin-bottom:var(--space-l)}

.reviews{display:grid;gap:var(--space-m);
  grid-template-columns:repeat(auto-fit,minmax(17rem,1fr));margin-bottom:var(--space-m)}
.review{background:var(--paper);border:var(--border);border-radius:var(--radius-l);
  padding:var(--space-m);display:flex;flex-direction:column;gap:var(--space-2xs)}
.review__stars{color:var(--brand);font-size:var(--step-1);letter-spacing:0.08em;line-height:1}
.review__body{flex:1}
.review__who{font-size:var(--step--1);color:var(--text-muted);font-weight:600}

/* ── Order permalink: stepper, status, tabs (SPEC §13, §5.4) ───────────────── */
.order{padding-block:var(--space-xl) var(--space-2xl)}
.order__title{font-size:var(--step-3);margin-bottom:var(--space-l)}
.order__foot{margin-top:var(--space-xl);color:var(--text-muted);font-size:var(--step--1)}

/* The stepper answers "where are we?" without scrolling. */
.stepper{display:flex;flex-wrap:wrap;gap:var(--space-2xs) 0;margin-bottom:var(--space-m);
  counter-reset:step;list-style:none;padding:0}
.step{display:flex;align-items:center;gap:var(--space-2xs);flex:1 1 auto;min-width:0;
  color:var(--text-muted);font-size:var(--step--1);font-weight:600}
.step:not(:last-child)::after{content:"";flex:1;height:2px;min-width:0.6rem;
  background:var(--rule);margin-inline:var(--space-2xs)}
.step__dot{width:1.9rem;height:1.9rem;border-radius:50%;display:grid;place-items:center;flex:none;
  background:var(--paper);border:2px solid var(--rule);font-size:var(--step--1);line-height:1;
  transition:background-color var(--dur-base) var(--ease-out),
             border-color var(--dur-base) var(--ease-out),
             transform var(--dur-base) var(--ease-out)}
.step__label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.step.is-done{color:var(--text)}
.step.is-done .step__dot{background:var(--teal);border-color:var(--teal);color:var(--paper)}
.step.is-done:not(:last-child)::after{background:var(--teal)}
.step.is-active{color:var(--ink)}
.step.is-active .step__dot{background:var(--brand);border-color:var(--brand);color:var(--paper);
  transform:scale(1.12)}
/* On a phone the labels do not fit; the dots and the status line below carry the meaning. */
@media (max-width:46rem){
  .step__label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
  .step{flex:1 1 0}
}

.now{background:var(--bg-raised);border-left:3px solid var(--brand);
  border-radius:0 var(--radius-m) var(--radius-m) 0;
  padding:var(--space-s) var(--space-m);margin-bottom:var(--space-l)}
.now__state{font-family:var(--font-display);font-size:var(--step-1);font-weight:600;
  display:flex;align-items:baseline;gap:var(--space-2xs);flex-wrap:wrap}
.now__round{font-family:var(--font-mono);font-size:var(--step--1);color:var(--text-muted)}
.now__what{color:var(--text-muted);font-size:var(--step--1);margin-top:var(--space-3xs)}

/* ── Tabs ──────────────────────────────────────────────────────────────────── */
.tabs__strip{display:flex;gap:var(--space-2xs);flex-wrap:wrap;
  border-bottom:2px solid var(--rule);margin-bottom:var(--space-m)}
/* `min-height:44px` because these are TAPPED, not clicked. `F-115`: measured 41px at 390px on the
 * customer's own order page -- 3px under the published minimum Apple and Google both use, on the
 * three controls (Přehled / Zprávy / Historie) a customer uses to find their messages. Small enough
 * to look fine in a screenshot and to be missed every time a thumb lands slightly off. */
.tab{min-height:44px;display:inline-flex;align-items:center;gap:var(--space-3xs);
  padding:var(--space-2xs) var(--space-s);text-decoration:none;font-weight:600;
  font-size:var(--step--1);color:var(--text-muted);
  border-bottom:3px solid transparent;margin-bottom:-2px;
  transition:color var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out)}
.tab:hover{color:var(--brand)}
.tab[aria-selected="true"]{color:var(--ink);border-bottom-color:var(--brand)}
.tab__count{background:var(--sand-deep);color:var(--ink);border-radius:999px;
  padding:0 0.45em;font-size:0.75em;font-family:var(--font-mono)}
/* Panels are ALL visible until the script says otherwise — no-JS degrades to a long page. */
.tabs--js .panel[hidden]{display:none}
.panel{animation:panel-in var(--dur-base) var(--ease-out)}
@keyframes panel-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
.tabs:not(.tabs--js) .panel{border-top:var(--border);padding-top:var(--space-m);
  margin-top:var(--space-m)}

/* ── Message direction ─────────────────────────────────────────────────────── */
.msg--customer{background:var(--terracotta-wash);margin-left:var(--space-l)}
.msg--operator{background:var(--bg-raised);margin-right:var(--space-l)}
.msg--system{background:var(--bg-raised);border:1px dashed var(--rule);
  color:var(--text-muted);font-size:var(--step--1)}
.msg__dir{font-weight:600;color:var(--text)}
.msg__meta{display:flex;gap:var(--space-s);align-items:baseline;flex-wrap:wrap;
  font-size:var(--step--1);color:var(--text-muted);margin-bottom:var(--space-3xs)}

.stars-pick{display:flex;gap:var(--space-2xs);flex-wrap:wrap}
.star{display:inline-flex;align-items:center;gap:var(--space-3xs);cursor:pointer;
  border:2px solid var(--rule);border-radius:var(--radius-m);padding:var(--space-3xs) var(--space-2xs);
  font-weight:600;font-size:var(--step--1)}
.star:has(input:checked){border-color:var(--brand);background:var(--terracotta-wash)}
.star input{accent-color:var(--brand)}

/* ── Toasts ────────────────────────────────────────────────────────────────── */
/* left+right+max-width+auto margin instead of min()/calc(): the W3C validator cannot resolve
   var() inside arithmetic and rejects it (same trap as the page container). This is also simpler. */
.toasts{position:fixed;right:var(--space-s);left:var(--space-s);bottom:var(--space-s);z-index:20;
  display:flex;flex-direction:column;gap:var(--space-2xs);align-items:stretch;
  max-width:24rem;margin-left:auto;pointer-events:none}
.toast{pointer-events:auto;display:flex;align-items:flex-start;gap:var(--space-s);
  background:var(--ink);color:var(--paper);border-radius:var(--radius-m);
  padding:var(--space-s) var(--space-s) var(--space-s) var(--space-m);
  box-shadow:var(--shadow-m);border-left:4px solid var(--teal);
  animation:toast-in var(--dur-base) var(--ease-out)}
.toast--err{border-left-color:var(--terracotta)}
.toast__body{flex:1;font-size:var(--step--1);line-height:1.45}
.toast__close{flex:none;background:none;border:0;color:inherit;cursor:pointer;
  font-size:1.35rem;line-height:1;padding:0 .2em;opacity:.7;border-radius:var(--radius-s)}
.toast__close:hover{opacity:1}
.toast.is-going{opacity:0;transform:translateY(6px);
  transition:opacity var(--dur-fast) var(--ease-out),transform var(--dur-fast) var(--ease-out)}
@keyframes toast-in{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.toast{animation:none}}

/* ── In-flight feedback: htmx sets aria-busy while a request is running ─────── */
.btn__spin{display:none;width:0.9em;height:0.9em;border-radius:50%;
  border:2px solid currentColor;border-top-color:transparent;animation:spin .6s linear infinite}
.htmx-request .btn__spin,.htmx-request.btn .btn__spin{display:inline-block}
.htmx-request .btn__label{opacity:.7}
.htmx-request button[type=submit]{pointer-events:none}
@keyframes spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.btn__spin{animation-duration:2s}}

/* ── Homepage headline rotator ─────────────────────────────────────────────── */
.hero__rotate{color:var(--brand)}
/* The caret that trails the typing. Distinct from the logo's beam on purpose — thinner and
   quieter, so the two marks do not read as the same object competing on screen. */
.hero__caret{display:inline-block;width:0.055em;height:0.78em;background:var(--brand);
  margin-left:0.08em;vertical-align:baseline;opacity:0.85}
@media (prefers-reduced-motion:no-preference){
  .hero__caret{animation:hero-caret 1.1s steps(1,end) infinite}
}
@keyframes hero-caret{0%,50%{opacity:0.85}51%,100%{opacity:0}}
/* Reserve the taller of the two wrapped states so a longer phrase cannot make the page jump. */
.hero__title{min-height:2.1em}

/* ── The wordmark animation ────────────────────────────────────────────────────
   A 20-second loop that tells the brand's own story:

     blink (a prompt, waiting)
       → the caret tips over to a horizontal underscore — "ready"
       → back upright, and "online" types itself in, PUSHING the caret along
       → the caret thickens into a solid block — a delete cursor
       → the block eats the word back off
       → it narrows to a caret again and resumes waiting

   TWO ELEMENTS, TWO JOBS. `.logo__cursor` owns the SHAPE on the 20s timeline;
   `.logo__beam` inside it owns the BLINK on its own fast cycle. One element cannot do both —
   opacity and transform would have to share a timeline, which is why the first version had to
   fake blinks inside the 20s keyframes and could not be sped up independently.

   ORDER MATTERS: the caret sits AFTER `.logo__online` in the markup, so every typed character
   displaces it rightwards. In the first version the caret came first and the word grew straight
   over the top of it.
   ────────────────────────────────────────────────────────────────────────────── */
/* 🚨 `overflow:hidden` IS THE ANIMATION. `type-online` animates `max-width`, which collapses this
   flex item's BOX — but overflow defaults to `visible`, so without this the glyphs paint straight
   through a zero-width box and "online" sits on screen for the whole 20s loop. It MEASURED
   perfectly (getBoundingClientRect width 0) and looked wrong; only a screenshot caught it.
   `max-width` here rather than in the keyframes is the no-animation default, so reduced-motion,
   print, and a stylesheet that half-loads all show the full wordmark instead of "chciweb". */
.logo__online{flex:none;overflow:hidden;white-space:nowrap;max-width:3.6em}

.logo__cursor{display:inline-flex;align-items:center;flex:none;
  width:0.085em;height:0.8em;margin-left:0.12em;
  transform-origin:50% 60%}
.logo__cursor .logo__beam{width:100%;height:100%;margin:0;
  background:var(--brand);border-radius:0.05em}

@media (prefers-reduced-motion:no-preference){
  .logo__cursor{animation:caret-shape 20s var(--ease-in-out) infinite}
  /* Faster, and on its OWN cycle so it never has to be re-timed when the loop changes. */
  .logo__cursor .logo__beam{animation:caret-blink 0.75s steps(1,end) infinite}
  .logo__online{animation:type-online 20s steps(6,end) infinite}
}

@keyframes caret-blink{0%,55%{opacity:1}56%,100%{opacity:0.12}}

@keyframes caret-shape{
  /* waiting */
  0%,   34% {width:0.085em; transform:rotate(0deg)}
  /* tip over to an underscore — "ready to type" */
  38%,  41% {width:0.085em; transform:rotate(90deg) translateY(-0.33em)}
  /* upright again, then the word types and pushes this along */
  44%,  68% {width:0.085em; transform:rotate(0deg)}
  /* thicken into a solid block: the delete cursor */
  72%,  76% {width:0.46em;  transform:rotate(0deg)}
  /* the block stays wide while it eats the word back off */
  86%       {width:0.46em;  transform:rotate(0deg)}
  /* narrow back to a caret and wait */
  90%, 100% {width:0.085em; transform:rotate(0deg)}
}

@keyframes type-online{
  0%,  44% {max-width:0;     margin-left:0}
  60%, 76% {max-width:3.6em; margin-left:0.16em}   /* typed, held, still there while the block forms */
  86%,100% {max-width:0;     margin-left:0}        /* eaten by the block cursor */
}

/* ── Renewal panel (SPEC §10, D-28) ──────────────────────────────────────────
   BOTH answers are real buttons of equal weight. A "no" hidden in small grey text is a dark
   pattern, and it also costs us the warning: a customer who cannot find it just stops replying,
   and we learn they are leaving when the mailbox bounces. */
.renewal{border:1px solid var(--rule);border-radius:var(--radius);
  padding:clamp(1.1rem,3vw,1.75rem);margin-block:1.25rem;background:var(--surface)}
.renewal--preselected{border-color:var(--brand);box-shadow:0 0 0 3px rgb(193 68 43 / 0.09)}
.renewal--confirmed{border-left:3px solid var(--brand)}
.renewal--declined{border-left:3px solid var(--rule);opacity:0.82}
.renewal__title{font-family:var(--font-display);font-size:var(--step-1);
  margin:0.25rem 0 0.9rem;line-height:1.2}
.renewal__answer{display:flex;flex-wrap:wrap;gap:0.7rem;margin-block:1.1rem}
.renewal__answer .btn{flex:1 1 14rem}
.tab__count--attention{background:var(--brand);color:#fff}

/* Draws the eye to the choice the emailed link named — without ever submitting it. */
@media (prefers-reduced-motion:no-preference){
  .btn--pulse{animation:btn-pulse 1.6s var(--ease-in-out) 3}
}
@keyframes btn-pulse{
  0%,100%{box-shadow:0 0 0 0 rgb(193 68 43 / 0)}
  50%    {box-shadow:0 0 0 6px rgb(193 68 43 / 0.16)}
}


/* ── Legal documents (SPEC §15, SEC-15) ──────────────────────────────────────
   Long-form prose, so the measure is tighter than the rest of the site: ~68 characters is where
   the eye stops losing the next line, and a contract is read carefully or not at all. */
.legal{padding-block:var(--space-xl) var(--space-2xl)}
.legal__body{max-width:68ch}
.legal__body h1{font-family:var(--font-display);font-size:var(--step-4);
  letter-spacing:-0.03em;line-height:1.12;margin:0 0 var(--space-m)}
.legal__body h2{font-family:var(--font-display);font-size:var(--step-2);
  margin:var(--space-l) 0 var(--space-2xs);line-height:1.2}
.legal__body h3{font-size:var(--step-1);margin:var(--space-m) 0 var(--space-3xs)}
.legal__body p,.legal__body li{color:var(--text);line-height:1.65}
.legal__body ul,.legal__body ol{padding-left:1.15em;display:grid;gap:0.35rem}
.legal__body blockquote{margin:var(--space-m) 0;padding:0.9rem 1.1rem;
  border-left:3px solid var(--brand);background:var(--surface);border-radius:0 var(--radius) var(--radius) 0}
.legal__body blockquote p:last-child{margin-bottom:0}
.legal__body hr{border:0;border-top:1px solid var(--rule);margin:var(--space-l) 0}
.legal__body a{color:var(--brand)}
.legal__body table{width:100%;border-collapse:collapse;margin:var(--space-m) 0;
  font-size:var(--step--1)}
.legal__body th,.legal__body td{text-align:left;padding:0.55rem 0.7rem;
  border-bottom:1px solid var(--rule);vertical-align:top}
.legal__body th{font-weight:600}
/* An open question is SHOWN, never smoothed into prose — a customer reading a draft is entitled
   to see which parts are not settled yet. */
.legal__todo{background:rgb(193 68 43 / 0.12);color:var(--brand);
  padding:0.05em 0.35em;border-radius:0.25em;font-weight:600;white-space:nowrap}
.legal__banner{border:1px solid var(--brand);border-radius:var(--radius);
  padding:clamp(0.9rem,2.5vw,1.35rem);margin-bottom:var(--space-l);background:var(--surface)}
.legal__banner p{margin:0 0 0.4rem}
.legal__banner p:last-child{margin-bottom:0}

/* ── Tables never force a horizontal scroll on a phone ───────────────────── */
/* Markdown emits a bare <table> with no wrapper to scroll, so the table itself becomes the scroll
   container. `table-layout:fixed` is what stops a long cell forcing the whole PAGE sideways —
   without it the row stays wider than the viewport and the body scrolls horizontally, which is the
   single most broken-feeling thing a phone layout can do. */
.legal__body table{table-layout:fixed}
.legal__body td,.legal__body th{overflow-wrap:anywhere}
@media (max-width:34rem){
  .legal__body table{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch;
    max-width:100%}
}

/* ── Showcase (SPEC §14, D-16) ───────────────────────────────────────────────
   A scroll-snap rail, not a carousel library. Swipe on a phone, drag or shift-scroll on a desktop,
   and it degrades to a plain row of images with JavaScript off — there is none to disable.

   🚨 NOTHING AUTO-ADVANCES. A slide that moves on its own is a slide nobody finishes looking at,
   and it steals focus from the reading position. The customer controls the pace. */
.work{list-style:none;padding:0;margin:var(--space-l) 0 0;display:grid;gap:var(--space-xl)}
.work__item{display:grid;gap:var(--space-2xs)}
.work__head{display:flex;align-items:baseline;gap:var(--space-s);flex-wrap:wrap}
.work__name{font-family:var(--font-display);font-size:var(--step-2);margin:0;line-height:1.2}
.work__name a{color:var(--ink);text-decoration:none}
.work__name a:hover{color:var(--brand)}
/* The label that keeps the portfolio honest — visible, not a footnote. */
.work__kind{margin:0;font-size:var(--step--1);color:var(--text-muted);
  border:1px solid var(--rule);border-radius:99em;padding:0.15em 0.7em;white-space:nowrap}
.work__kind--own{border-color:var(--brand);color:var(--brand)}
.work__summary{color:var(--text-muted);max-width:60ch;margin:0 0 var(--space-2xs)}

/* `align-items:flex-start` because flex defaults to `stretch`: a tall phone capture next to a wide
   desktop one stretched the desktop frame to match, leaving a slab of empty card below the image.
   Each frame now keeps its own height, which is also what makes the two shapes read as two
   DEVICES rather than as one broken grid. */
.shots{display:flex;align-items:flex-start;gap:var(--space-s);overflow-x:auto;
  scroll-snap-type:x mandatory;
  padding-block:var(--space-2xs) var(--space-xs);
  scrollbar-width:thin;-webkit-overflow-scrolling:touch}
/* Keyboard users scroll this with arrow keys, so it must show where focus is. */
.shots:focus-visible{outline:2px solid var(--brand);outline-offset:4px;border-radius:var(--radius)}
.shot{scroll-snap-align:start;flex:0 0 auto;margin:0;
  width:min(38rem, 82vw);border-radius:var(--radius);overflow:hidden;
  border:1px solid var(--rule);background:var(--surface)}
/* A phone capture is tall and narrow; giving it the desktop width would letterbox it into
   uselessness — the whole point is showing the customer it works on THEIR screen. */
.shot--mobile{width:min(15rem, 46vw)}
/* A phone capture is ~2.2x taller than it is wide; without a cap it sets the row height and the
   desktop shot beside it looks like a postage stamp. Cropping from the top keeps the part a
   customer actually judges — the header and the first screen. */
.shot--mobile img{max-height:26rem;object-fit:cover;object-position:top}
.shot img{display:block;width:100%;height:auto}
.shot figcaption{padding:0.5rem 0.75rem;font-size:var(--step--1);color:var(--text-muted)}

@media (prefers-reduced-motion:no-preference){
  .shots{scroll-behavior:smooth}
}

/* 🚨 ON A PHONE THE RAIL BECOMES A COLUMN. A horizontal scroller sets its height from its TALLEST
   child, so a 386px phone capture sitting off-screen to the right left ~200px of visible nothing
   under the 199px desktop shot — measured 2026-08-05. The customer sees an empty card and no hint
   that anything is beside it.

   Stacking removes the dead space, removes the need to discover a sideways swipe on the one device
   where the natural gesture is vertical, and lets each shot keep its true proportions. The rail is
   the right shape from `sm` up, where two frames fit side by side and the comparison is the point. */
@media (max-width:34rem){
  .shots{display:grid;grid-auto-flow:row;gap:var(--space-s);
    overflow-x:visible;scroll-snap-type:none}
  .shot{width:100%}
  /* Still narrower than the desktop frame — it has to READ as a phone. */
  .shot--mobile{width:min(15rem,62%)}
  .shot--mobile img{max-height:none}
}

/* The promised date (SPEC §4, D-30). Given weight, because it answers the question the customer
   actually came to the page with. */
.now__promise{display:flex;align-items:baseline;gap:0.45em;margin:0.6rem 0 0;
  font-size:var(--step-0);color:var(--text)}
.now__promise-icon{color:var(--brand);font-size:0.9em}
/* Late is stated, not hidden — they can already see the date has passed. */
.now__promise--late{color:var(--brand)}

/* ── Operator console (SPEC §16, D-31) ───────────────────────────────────────
   Verbs, not states. Dense on purpose: this is a worklist read many times a day, not a page to be
   admired, so the eye should reach the next action without scrolling past decoration. */
.console{padding-block:var(--space-l) var(--space-2xl)}
.console__title{font-family:var(--font-display);font-size:var(--step-4);margin:0 0 var(--space-l)}
.bucket{margin-bottom:var(--space-xl)}
.bucket__title{font-family:var(--font-display);font-size:var(--step-2);
  display:flex;align-items:baseline;gap:0.5em;margin:0 0 0.15rem}
.bucket__count{font-size:var(--step--1);color:var(--text-muted);
  border:1px solid var(--rule);border-radius:99em;padding:0.1em 0.6em}
.bucket--urgent .bucket__count{border-color:var(--brand);color:var(--brand)}
.bucket__hint{color:var(--text-muted);font-size:var(--step--1);margin:0 0 var(--space-s);max-width:62ch}
.bucket__empty{color:var(--text-muted);font-size:var(--step--1);margin:0}

.jobs{list-style:none;padding:0;margin:0;display:grid;gap:var(--space-2xs)}
.job{display:flex;flex-wrap:wrap;gap:var(--space-s);justify-content:space-between;
  align-items:flex-start;padding:var(--space-s);border:1px solid var(--rule);
  border-radius:var(--radius);background:var(--surface)}
.bucket--urgent .job{border-left:3px solid var(--brand)}
.job__who{min-width:min(22rem,100%);flex:1 1 22rem}
.job__name{font-weight:600;color:var(--ink);text-decoration:none;
  display:inline-flex;align-items:center;min-height:2.25rem}
.job__name:hover{color:var(--brand)}
.job__meta,.job__detail,.job__contact{margin:0.1rem 0 0;font-size:var(--step--1);color:var(--text-muted)}
.job__detail{color:var(--brand)}
.job__late{color:var(--brand)}
.job__do{display:flex;flex-wrap:wrap;gap:0.45rem;align-items:flex-start}
.job__do form{margin:0}
.console__foot{color:var(--text-muted);font-size:var(--step--1);margin-top:var(--space-xl)}

/* ── Partial payment (SPEC §9.3, D-32) ───────────────────────────────────────
   A customer who has paid something is not a customer who has not paid. The block leads with what
   we RECEIVED and thanks them, then states the remainder — the reverse order reads as a demand. */
.partial{border:1px solid var(--rule);border-left:3px solid var(--brand);
  border-radius:0 var(--radius) var(--radius) 0;padding:0.9rem 1.1rem;margin-block:1rem;
  background:var(--surface)}
.partial__head{margin:0 0 0.6rem}
.partial__rest{margin:0.6rem 0 0;font-size:var(--step--1);color:var(--text-muted)}
.partial__bar{height:0.5rem;border-radius:99em;background:var(--rule);overflow:hidden}
.partial__bar span{display:block;height:100%;background:var(--brand);border-radius:99em}

/* ── Console: the conversation (SPEC §13, D-34) ──────────────────────────────
   Answering a customer without their question in front of you means opening another tab first,
   which is how a reply becomes "later". */
.job__said{margin:0.5rem 0 0;padding:0.5rem 0.75rem;border-left:2px solid var(--rule);
  background:var(--paper);border-radius:0 var(--radius) var(--radius) 0}
.job__said p{margin:0;font-size:var(--step--1)}
.job__said cite{display:block;margin-top:0.2rem;font-size:var(--step--1);
  color:var(--text-muted);font-style:normal}
.job__reply{margin-top:0.5rem}
.job__reply > summary{cursor:pointer;font-size:var(--step--1);color:var(--brand);
  display:inline-flex;align-items:center;gap:0.4em;min-height:2.75rem}
.job__reply textarea{width:100%;margin:0.4rem 0;min-height:5rem}
.job__flag{background:var(--brand);color:#fff;border-radius:99em;
  padding:0.05em 0.5em;font-size:0.85em}

/* ── Domain availability (SPEC §6) ───────────────────────────────────────────
   Three tones, and UNKNOWN gets its own — it must never look like the green one. */
.domain-check{display:flex;gap:0.55em;align-items:flex-start;margin:0.5rem 0 0;
  font-size:var(--step--1);line-height:1.5}
.domain-check__dot{flex:none;width:0.6em;height:0.6em;border-radius:50%;margin-top:0.45em}
.domain-check--ok{color:#1d6b3f}
.domain-check--ok .domain-check__dot{background:#1d6b3f}
.domain-check--no{color:var(--brand)}
.domain-check--no .domain-check__dot{background:var(--brand)}
.domain-check--warn{color:var(--text-muted)}
.domain-check--warn .domain-check__dot{background:var(--text-muted)}

/* ── What we commit to, said before they commit (SPEC §4.1, D-30) ──────────── */
.promises{list-style:none;padding:0;margin:0 0 var(--space-l);display:grid;gap:0.5rem}
.promises li{position:relative;padding-left:1.5em;color:var(--text-muted);
  font-size:var(--step--1);line-height:1.55}
.promises li::before{content:"✓";position:absolute;left:0;top:0;color:var(--brand);
  font-weight:700}
.promises strong{color:var(--ink)}
.section-page{padding-top:var(--space-l)}

/* ── "Vaše služby" (SPEC §13, D-37) ─────────────────────────────────────────── */
.services__title{font-family:var(--font-display);font-size:var(--step-1);
  margin:var(--space-m) 0 0.4rem}
.mailboxes{list-style:none;padding:0;margin:0 0 0.8rem;display:grid;gap:0.4rem}
.mailboxes li{display:flex;flex-wrap:wrap;gap:0.6em;align-items:baseline;
  padding:0.5rem 0.75rem;border:1px solid var(--rule);border-radius:var(--radius);
  background:var(--surface)}

/* The numbers before the click (SPEC §4). Hesitation at approval is the most expensive kind. */
.actions__preview{margin:0.35rem 0 0;font-size:var(--step--1);color:var(--text-muted)}
.actions__preview strong{color:var(--ink)}

/* ── Domain alternatives (SPEC §6, D-39) ─────────────────────────────────── */
.alts{margin-top:0.6rem;padding:0.8rem 1rem;border:1px solid var(--rule);
  border-radius:var(--radius);background:var(--surface)}
.alts__head{margin:0 0 0.5rem;font-size:var(--step--1)}
.alts__list{list-style:none;padding:0;margin:0;display:grid;gap:0.45rem}
.alts__list li{display:flex;flex-wrap:wrap;gap:0.6em;align-items:baseline}
.alts__pick{border:1px solid var(--brand);background:transparent;color:var(--brand);
  border-radius:99em;padding:0.25em 0.9em;font:inherit;font-weight:600;cursor:pointer;
  min-height:2.25rem}
.alts__pick:hover{background:var(--brand);color:#fff}
.alts__why{font-size:var(--step--1);color:var(--text-muted);flex:1 1 14rem}
.alts__pending{margin-top:0.5rem;opacity:0.75}

/* ── Provisioning steps in the console (SPEC §7, D-41) ────────────────────── */
.job__step{margin:0.3rem 0;font-size:var(--step--1);padding-left:0.7em;
  border-left:2px solid var(--rule)}
.job__step--done{border-left-color:#1d6b3f}
.job__step--failed{border-left-color:var(--brand);color:var(--brand)}
.job__step--skipped{opacity:0.65}


/* 🚨 `[hidden]` MUST WIN. The UA stylesheet gives `[hidden]{display:none}`, but ANY author rule
   setting `display` on the same element overrides it — and then JavaScript that sets `el.hidden`
   silently does nothing. Measured 2026-08-11: the new intake steps used `class="step"`, which
   already existed in this file as `.step{display:flex}` for the process stepper, so every step
   stayed visible while the JS believed it had hidden them. The classes were renamed (that was the
   root fix); this rule stops the NEXT collision from being invisible. */
[hidden]{display:none !important}

/* ── Intake steps, summary and the terms overlay — SPEC §5.1/§5.6, `D-54` ────────────
   Every rule here styles something JavaScript reveals. With JS off none of these elements
   is shown (the nav is `hidden`, the progress list is empty), so the form falls back to the
   plain page it has always been — §17 forbids a build chain and this adds none. */
.formsteps{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;margin:0 0 1.6rem;padding:0;
  font:500 .82rem/1.3 var(--font-body)}
/* `F-124` — on a phone the four labelled chips measured 149+88+150+98 = **485px** of content in
   **342px** of room, so they wrapped 2+2 and ate two lines of the most valuable screen a customer
   sees. Below 30rem the chips become NUMERIC and fit one line.
   The words are not deleted, they move: each step's own `<legend>` already states the title in full
   right below the stepper, so the visible text is not lost — and the label stays in the accessibility
   tree via the same clip that `.visually-hidden` uses, rather than `display:none`, so a screen
   reader still reads "3 Doména a web" and not a bare "3". */
@media (max-width:30rem){
  .formsteps__t{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap;border:0}
  .formsteps__item{gap:0;padding:.3rem}
}
.formsteps__item{display:flex;align-items:center;gap:.4rem;padding:.35rem .7rem;border-radius:999px;
  background:var(--sand);color:var(--text-muted)}
.formsteps__item.is-current{background:var(--terracotta-wash);color:var(--brand)}
.formsteps__item.is-done{color:var(--text)}
.formsteps__n{display:grid;place-items:center;width:1.4rem;height:1.4rem;border-radius:50%;
  background:var(--paper);font-size:.75rem}
.formsteps__item.is-current .formsteps__n{background:var(--brand);color:var(--paper)}

.formstep-nav{display:flex;gap:.8rem;margin-top:1.6rem}
.formstep-nav .btn{flex:1 1 auto}

.summary__list{display:grid;grid-template-columns:auto 1fr;gap:.4rem 1rem;margin:0 0 1.6rem;
  padding:1rem 1.2rem;background:var(--sand);border-radius:.6rem}
.summary__list dt{color:var(--text-muted);font-size:.85rem}
.summary__list dd{margin:0;font-weight:600}
.summary__empty{color:var(--text-muted)}

/* `<dialog>` gives Esc, focus trapping and inert background for free — re-implementing those
   in a div is how an overlay becomes a keyboard trap. */
.overlay{max-width:44rem;width:calc(100% - 2rem);max-height:80vh;overflow:auto;border:0;
  border-radius:.8rem;padding:1.6rem;background:var(--bg-raised);color:var(--text)}
.overlay::backdrop{background:rgb(0 0 0 / .45)}
.overlay__body{margin-bottom:1.2rem}
.overlay__body h2{margin-top:0}

@media (prefers-reduced-motion:reduce){
  .formsteps__item,.overlay{transition:none}
}

/* The process component (`web/_process.html`, `D-36`) is a FULL-WIDTH page section: it carries
   `.section` padding and a `.wrap` max-width. Dropped verbatim into a narrow form step it
   overflowed — measured 2026-08-11, a 390 px viewport produced a 741 px scrollWidth, i.e.
   horizontal scrolling on a phone, which ask #8 (desktop/mobile parity) rules out.
   It is INCLUDED rather than re-written, because `D-36` makes it the one copy of "how this works";
   only its page-level layout is neutralised here. */
.formstep .section{padding:0;margin:0}
.formstep .wrap{max-width:none;width:100%;padding:0}
.formstep .steps{grid-template-columns:1fr}
.formstep .step{display:grid;grid-template-columns:auto 1fr;gap:.6rem;align-items:start}

/* ── Field affordances — SPEC §5.1 property 4 (tips · patterns · icons) ─────────────────────────
   The icon is a background image on the INPUT, not an extra element: an <img> or <svg> sibling
   would be one more thing between the label and the control for a screen reader to read out, and
   the icon carries no information the label does not already give. Decorative, so it is invisible
   to assistive tech by construction.

   `background-image` with an inline SVG data URI — no sprite, no extra request, and no dependency
   on a font that might not load. `currentColor` cannot be used inside a data URI, so the stroke is
   the muted token's literal value; if the palette moves, these move with it by hand. */
.field--icon input{padding-left:2.4rem;background-repeat:no-repeat;
  background-position:0.75rem center;background-size:1.1rem 1.1rem}
.field--icon-mail input{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a7f76' stroke-width='2'%3E%3Crect x='2' y='4' width='20' height='16' rx='2'/%3E%3Cpath d='m2 7 10 6 10-6'/%3E%3C/svg%3E")}
.field--icon-phone input{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a7f76' stroke-width='2'%3E%3Cpath d='M5 3h4l2 5-3 2a14 14 0 0 0 6 6l2-3 5 2v4a2 2 0 0 1-2 2A17 17 0 0 1 3 5a2 2 0 0 1 2-2z'/%3E%3C/svg%3E")}
.field--icon-user input{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a7f76' stroke-width='2'%3E%3Ccircle cx='12' cy='8' r='4'/%3E%3Cpath d='M4 21a8 8 0 0 1 16 0'/%3E%3C/svg%3E")}
.field--icon-id input{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a7f76' stroke-width='2'%3E%3Crect x='2' y='5' width='20' height='14' rx='2'/%3E%3Cpath d='M7 10h4M7 14h7'/%3E%3C/svg%3E")}
.field--icon-pin input{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a7f76' stroke-width='2'%3E%3Cpath d='M12 21s7-6.2 7-11a7 7 0 1 0-14 0c0 4.8 7 11 7 11z'/%3E%3Ccircle cx='12' cy='10' r='2.5'/%3E%3C/svg%3E")}

/* The format tip. Quieter than help text: it is a hint about SHAPE, not an instruction. */
.field__tip{font-size:var(--step--1);color:var(--muted);margin:0.25rem 0 0}

/* An inline complaint. `[hidden]` is honoured explicitly because a `display` rule elsewhere in this
   file would otherwise win over the UA stylesheet and paint an empty red line under every field —
   the class of collision no DOM assertion can see. */
.field__error[hidden]{display:none}
.field input[aria-invalid="true"]{border-color:var(--danger,#b3261e)}

/* ── F-62 · a step that still holds an error says so ────────────────────────────────────────────
   Navigating to the first error cannot reveal errors on OTHER steps — showing one step hides the
   rest — so the chip carries the fact. Colour AND words: a red chip tells a screen-reader user
   nothing, which is why `formsteps__bad` is real text rather than an ::after glyph. */
.formsteps__item.is-error{border-color:var(--danger,#b3261e)}
.formsteps__bad{font-size:var(--step--2,0.75rem);color:var(--danger,#b3261e);font-weight:600}

/* The ARES confirm card's action row (`D-54`(c)). Added 2026-08-17: the class shipped on
   2026-08-15 with NO rule behind it, and the frontend kit's orphan-class check found it on its
   first run here — a wrapper that does nothing looks exactly like a wrapper that works. */
.ares__actions{display:flex;flex-wrap:wrap;gap:var(--space-2xs,0.5rem);margin-top:var(--space-xs,0.75rem)}

/* ── Mailbox migration (SPEC §7.7, `D-43`) — `F-65` ─────────────────────────────────────────────
   Seven classes shipped in `_migration.html` with NO rule behind any of them. Found by the
   frontend kit's orphan-class check on its first run here, 2026-08-17. This is the screen a
   customer watches while their mail is being moved: unstyled, the progress bar was not a bar and
   the error was indistinguishable from ordinary prose.

   Built from the project's own tokens — no new ones. `--teal` for progress because it is the
   "working" colour elsewhere on the site, `--terracotta` reserved for the error so the two states
   can never be confused at a glance. */
.migration{margin-top:var(--space-l)}
.migration > p{max-width:var(--measure)}

/* THE BAR. `--teal-deep` fill on a `--bg-raised` track, with the track visible even at 0% so a
   migration that has not started yet still LOOKS like a thing that will fill. */
.progress{
  height:0.6rem;background:var(--bg-raised);border:1px solid var(--border);
  border-radius:var(--radius-xl);overflow:hidden;margin-block:var(--space-2xs)}
.progress__bar{
  height:100%;background:var(--teal-deep);border-radius:inherit;
  /* Width is set inline from the job's own count. Animated so a poll every 3s reads as motion
     rather than a jump — and switched off for anyone who asked for less. */
  transition:width var(--dur-slow) var(--ease-out)}
@media (prefers-reduced-motion:reduce){.progress__bar{transition:none}}

/* A migration in flight: tinted panel, so "something is happening on our side" is legible without
   reading a word of it. */
.migration__progress{
  background:var(--bg-raised);border:1px solid var(--border);border-left:3px solid var(--teal-deep);
  border-radius:var(--radius-m);padding:var(--space-s)}
.migration__progress > p:first-child{margin-top:0}

/* DONE and ERROR must be distinguishable at a glance and NOT by colour alone — each carries its
   own border weight and its own leading word in the markup ("Hotovo" / "Přenos se nepodařil"). */
.migration__done{
  background:var(--bg-raised);border-left:3px solid var(--teal-deep);
  border-radius:var(--radius-s);padding:var(--space-xs) var(--space-s)}
.migration__error{
  background:var(--terracotta-wash);border-left:3px solid var(--terracotta-deep);
  border-radius:var(--radius-s);padding:var(--space-xs) var(--space-s)}

/* The provider hint block (`_migration_help.html`). Quieter than the panel above it: it is help,
   not state. */
.migration__help{
  margin-top:var(--space-2xs);padding-left:var(--space-s);
  border-left:2px solid var(--border);color:var(--text-muted);font-size:var(--step-0)}

/* ── Situational discounts (SPEC §2.2, `D-61`) ───────────────────────────────────────────────────
   Maker: "outlined and well visible" — so a real 2px brand outline, not a tint that reads as a
   disabled card. It sits BELOW the price cards and must not compete with them: no shadow, no
   background fill, one accent colour used for the percentage only. */
.deals{margin-top:var(--space-l);border:2px solid var(--brand);border-radius:.8rem;
  padding:var(--space-s) var(--space-s) var(--space-2xs)}
.deals__title{font-family:var(--font-display);font-size:var(--step-1);
  margin:0 0 var(--space-2xs)}
.deals__list{list-style:none;margin:0;padding:0;display:grid;gap:var(--space-2xs)}
/* The row wraps as a whole on a phone rather than letting the price orphan onto its own line:
   `deal__price` carries `margin-left:auto` only once there is room for it. */
.deal{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;padding:.4rem 0;
  border-top:var(--border)}
.deal:first-child{border-top:0}
.deal__percent{font-family:var(--font-mono);font-weight:600;color:var(--brand);
  background:var(--terracotta-wash);border-radius:999px;padding:.15rem .55rem;white-space:nowrap}
.deal__label{font-weight:500;flex:1 1 8rem;min-width:0}
.deal__price{margin-left:auto;white-space:nowrap}
.deal__was{color:var(--text-muted);margin-right:.4rem}

/* The "i" disclosure. `<summary>` needs `list-style:none` AND the WebKit pseudo-element to lose its
   marker triangle — one without the other leaves a stray arrow on Safari only, which is the kind of
   defect that ships because nobody tested the browser they don't use. */
.tip{display:inline}
.tip__btn{display:inline-grid;place-items:center;width:1.45rem;height:1.45rem;border-radius:50%;
  border:1px solid var(--rule);color:var(--text-muted);font:600 .8rem/1 var(--font-mono);
  cursor:pointer;list-style:none;user-select:none}
.tip__btn::-webkit-details-marker{display:none}
.tip__btn:hover,.tip__btn:focus-visible{border-color:var(--brand);color:var(--brand)}
.tip[open] .tip__btn{background:var(--brand);border-color:var(--brand);color:var(--paper)}
/* Full-row width once open: the explainer is two sentences, and squeezing it beside the price is how
   an explanation becomes a scroll. */
.tip__body{flex-basis:100%;margin:.5rem 0 0;color:var(--text-muted);font-size:var(--step--1)}

/* ── The homepage work TEASER (`F-134`) ──────────────────────────────────────────────────────────
   `[V]` measured on production 2026-08-18: `.prace` was **32 % of the homepage** — 1 682px desktop,
   2 296px phone, at ~950px per project on a phone. That is more than a full screen to see ONE
   reference, on the section a visitor scrolls past to reach the price list.

   The teaser shows three projects, one screenshot each, two-up on desktop, with the shot cropped to a
   fixed height so a tall capture cannot set the row height. `/prace/` is untouched and stays generous —
   that page is *for* looking at the work. Same template, one copy of the content (`D-36`). */
.work--compact{gap:var(--space-l)}
@media (min-width:58rem){
  .work--compact{grid-template-columns:1fr 1fr}
}
/* No rail in the teaser: one shot, so the horizontal scroller is machinery with nothing to scroll. */
.work--compact .shots{overflow:visible;padding-block:0 var(--space-2xs);scroll-snap-type:none}
.work--compact .shot{width:100%;scroll-snap-align:none}
/* `object-position:top` keeps the header and first screen — the part a customer actually judges. */
.work--compact .shot img{height:11rem;object-fit:cover;object-position:top}
.work--compact .shot figcaption{display:none}
.work__more{margin-top:var(--space-m)}

/* `F-134` — this block measured **480px on a phone**, a fifth of a screen for two lines of offer: the
   percentage chip, the label, the "i" and the price each claimed their own row once the flex wrapped.
   The tightening lives in the definitions above rather than in an override down here, because a class
   defined twice means whichever comes last wins and the other reads as a rule that is simply ignored —
   which `tests/test_css_hygiene.py` refuses, and it caught me doing exactly that. */
.deals .note--inline{margin-top:var(--space-2xs);font-size:var(--step--2)}

/* ── "We are thinking" (`F-135`) ─────────────────────────────────────────────────────────────────
   The domain-alternatives wait is a 31B model plus one registry check per candidate — several seconds,
   every time. The previous affordance was a static sentence, which after two seconds is
   indistinguishable from a sentence that has stopped. */
.thinking{display:flex;align-items:center;gap:.5rem}
.thinking__spin{width:.85rem;height:.85rem;flex:none;border-radius:50%;
  border:2px solid var(--rule);border-top-color:var(--brand);animation:thinking-spin .7s linear infinite}
/* Three dots that fill in, so the line reads as progressing even if the spinner is off. */
.thinking__dots::after{content:"";animation:thinking-dots 1.2s steps(1,end) infinite}
@keyframes thinking-spin{to{transform:rotate(360deg)}}
@keyframes thinking-dots{0%{content:""}25%{content:"."}50%{content:".."}75%{content:"..."}}
/* Motion is the affordance, never the message: with motion suppressed the sentence still says what is
   happening, and the dots settle on a static ellipsis rather than vanishing. */
@media (prefers-reduced-motion:reduce){
  .thinking__spin{animation:none;border-top-color:var(--rule)}
  .thinking__dots::after{content:"…";animation:none}
}

/* ── The generated brief (SPEC §5.3, `D-64`) ─────────────────────────────────────────────────────
   Visibly OURS-BUT-MACHINE-MADE: an inset panel with the brand's wash, so it never reads as the
   customer's own text nor as a system error. `INV-NO-SILENT-LLM` requires the label to be words, and the
   colour here only reinforces what the sentence already says. */
.brief{margin-top:var(--space-xs);padding:var(--space-s);border-radius:.6rem;
  background:var(--terracotta-wash);border:1px solid var(--rule)}
.brief__badge{margin:0 0 var(--space-2xs);font-size:var(--step--2);color:var(--brand);font-weight:600}
.brief__summary{margin:0 0 var(--space-xs)}
.brief__facts{display:grid;grid-template-columns:auto 1fr;gap:.2rem .8rem;margin:0 0 var(--space-xs);
  font-size:var(--step--1)}
.brief__facts dt{color:var(--text-muted)}
.brief__facts dd{margin:0}
.brief__reason{margin-top:var(--space-2xs)}
