/* ============================================================================
   ANOVEI — panel-demo.css
   Animowane demonstracje panelu („Panel w akcji") na stronach ofertowych.

   PO CO TO JEST
   Sekcja galerii pokazywała statyczne zrzuty: widać na nich ekran, ale nie
   widać PRACY. Kupujący ocenia narzędzie po tym, ile ruchów kosztuje go
   typowa czynność, a tego zrzut nie powie. Tutaj każda scena gra jeden
   scenariusz w schemacie: akcja użytkownika → reakcja interfejsu → rezultat.

   DLACZEGO CZYSTY CSS, A NIE TAILWIND
   Arkusz Tailwinda jest budowany statycznie (scripts/tailwind), więc każda nowa
   klasa użyta w szablonie wymaga przebudowy. Replika panelu to kilkadziesiąt
   komponentów o wymiarach spoza skali Tailwinda — trzymanie ich tutaj zdejmuje
   krok builda z każdej korekty sceny.

   JEDNOSTKA --u
   Replika nie jest skalowana transformem (rozmyte krawędzie i nieczytelny tekst
   przy zjeździe w dół). Zamiast tego wszystkie wymiary liczą się z jednej
   zmiennej --u, płynnej względem szerokości kontenera (container query units).
   Zasada: NIGDY `em` (składa się w zagnieżdżeniach) — zawsze calc(var(--u)*N).

   RUCH
   Silnik (js/panel-demo.js) nadaje scenie zbiór aktywnych stanów; element
   z `data-when="modal"` dostaje klasę .is-on, gdy „modal" jest w zbiorze.
   Wejścia i wyjścia robią przejścia CSS — stąd płynność bez klatkowania i bez
   ani jednej klatki kluczowej na scenę.
   ========================================================================= */

/* ── Sekcja ──────────────────────────────────────────────────────────────── */
.anv-demo { margin-bottom: 5rem; }

/* Wariant do kolumny obrazka w feature-row: samo okno, bez nagłówka, rozdziałów
   i paska kroków — sąsiednia kolumna z tekstem już opowiada, co się dzieje. */
.anv-demo--inline { margin-bottom: 0; width: 100%; min-width: 0; }
.anv-demo--inline .anv-demo__window { width: 100%; max-width: 560px; border-radius: 12px; }

/* W feature-row kolumna zwykłego obrazka może brać szerokość z samego pliku,
   ale demo ma wewnątrz sceny pozycjonowane absolutnie i szerokość procentową.
   Przy `lg:w-auto` nie dawało to stabilnego rozmiaru bazowego — kolumna mogła
   zwinąć się niemal do zera. Demo dostaje więc jawny udział w wierszu. */
.feature-row__media--demo {
    width: 100%;
    min-width: 0;
}
@media (min-width: 1024px) {
    .feature-row__media--demo {
        width: min(46%, 560px);
        flex-shrink: 0;
    }
}

.anv-demo__head { text-align: center; margin-bottom: 1.75rem; }

/* Pasek rozdziałów — przełącznik scenariuszy. */
.anv-demo__rail {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .5rem;
    margin-bottom: 1rem;
}
.anv-demo__tab {
    position: relative;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    padding: .6rem 1.05rem;
    border: 1px solid rgb(var(--c-border, 227 232 238));
    border-radius: 10px;
    background: #fff;
    color: rgb(var(--c-ink-2, 75 85 99));
    font-size: .875rem;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    transition: color .18s ease, border-color .18s ease, background-color .18s ease, box-shadow .18s ease;
}
.anv-demo__tab:hover { color: rgb(var(--c-ink, 17 28 37)); border-color: rgb(var(--c-accent-500, 71 145 178)); }
.anv-demo__tab[aria-selected="true"] {
    color: #fff;
    background: rgb(var(--c-ink-900, 15 27 38));
    border-color: rgb(var(--c-ink-900, 15 27 38));
    box-shadow: 0 6px 18px -8px rgb(15 27 38 / 55%);
}
.anv-demo__tab i { font-size: .8em; opacity: .85; }

/* Pasek postępu rozdziału — pełznie przez czas trwania scenariusza. Szerokość
   i czas przejścia ustawia silnik, więc pasek jest zegarem sceny, a nie osobną
   animacją, która mogłaby się z nią rozjechać. */
.anv-demo__tab-progress {
    position: absolute;
    left: 0; bottom: 0;
    height: 2px;
    width: 0;
    background: rgb(var(--c-accent-300, 127 196 221));
    transition: width linear 0s;
}

/* ── Okno przeglądarki ───────────────────────────────────────────────────── */
/* Kontener zapytań siedzi na OKNIE, nie na scenie. Element nie może odpytać
   sam siebie: gdy `container-type` stał na .anv-stage, reguły `@container`
   celujące w .anv-stage szukały kontenera wyżej — nie znajdowały żadnego
   i nigdy nie wchodziły, więc scena trzymała 16/10 także na telefonie.
   Okno ma tę samą szerokość co scena, więc jednostki cqi się nie zmieniają. */
.anv-demo__window {
    container-type: inline-size;
    position: relative;
    max-width: 1120px;
    margin: 0 auto;
    border-radius: 14px;
    background: #fff;
    border: 1px solid rgb(var(--c-border, 227 232 238));
    box-shadow: 0 28px 60px -30px rgb(15 27 38 / 42%), 0 6px 18px -12px rgb(15 27 38 / 25%);
    overflow: hidden;
}

.anv-demo__chrome {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .55rem .85rem;
    background: linear-gradient(180deg, #f9fafb, #f2f4f7);
    border-bottom: 1px solid rgb(var(--c-border, 227 232 238));
}
.anv-demo__dots { display: flex; gap: 6px; flex: 0 0 auto; }
.anv-demo__dots i { width: 10px; height: 10px; border-radius: 50%; display: block; }
.anv-demo__dots i:nth-child(1) { background: #f0685c; }
.anv-demo__dots i:nth-child(2) { background: #f5bf4f; }
.anv-demo__dots i:nth-child(3) { background: #61c454; }
.anv-demo__url {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    max-width: 30rem;
    margin: 0 auto;
    padding: .28rem .7rem;
    background: #fff;
    border: 1px solid rgb(var(--c-border, 227 232 238));
    border-radius: 999px;
    font-size: .72rem;
    color: rgb(var(--c-ink-3, 107 114 128));
}
.anv-demo__url i { color: #16a34a; font-size: .7em; flex: 0 0 auto; }
/* Zmiana adresu przy przejściu między widokami: krótkie przenikanie tekstu
   zamiast podmiany w jednej klatce — inaczej czyta się jak usterka renderu. */
.anv-demo__url span { transition: opacity .2s ease; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.anv-demo__url.is-switching span { opacity: 0; }
.anv-demo__live {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-size: .66rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-3, 107 114 128));
}
.anv-demo__live b {
    width: 6px; height: 6px; border-radius: 50%;
    background: #16a34a;
    animation: anv-live 2.4s ease-in-out infinite;
}
@keyframes anv-live { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

/* ── Scena ───────────────────────────────────────────────────────────────── */
/* Skala repliki stoi na jednostkach cqi liczonych od okna (patrz wyżej).

   IZOLACJA OD STYLÓW SERWISU
   Replika żyje w dokumencie, który ma własne reguły na gołych selektorach
   elementów (assets/style.css: `nav`, `nav ul`, `nav li`, `textarea`, `form sup`).
   Menu repliki było kiedyś `<nav>` i dziedziczyło po pasku serwisu
   `position: fixed; width: 100%; z-index: 999` — scena rozjeżdżała się na całą
   szerokość okna. Dziś menu jest `<div>`, a reset niżej pilnuje, żeby żadna
   przyszła reguła globalna nie wyrwała elementów sceny z układu. */
.anv-stage :where(div, span, b, i, svg, label, p) {
    position: static;
    inset: auto;
    float: none;
    width: auto;
    min-height: 0;
    max-width: none;
    z-index: auto;
}

/* Model pudełka scena musi ustawić SAMA, a nie liczyć na reset gospodarza.
   Na stronie ofertowej border-box wnosi preflight Tailwinda i wszystko się
   zgadza — ale scena bywa oglądana bez tego arkusza (podgląd roboczy), a wtedy
   content-box DODAJE wypełnienie do wysokości: obszar słupków wykresu urastał
   o swój `padding-bottom` i pochylone podpisy osi wychodziły poza kafel na
   nagłówek tabeli. Deklaracja niżej zdejmuje zależność od cudzego resetu. */
.anv-stage, .anv-stage *, .anv-stage *::before, .anv-stage *::after { box-sizing: border-box; }

.anv-stage {
    position: relative;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    background: #f9fafb;

    --u: clamp(9px, 1.24cqi, 13.5px);

    /* PALETA = PALETA PANELU.
       Wartości niżej nie są dobrane „na oko" do strony — to dokładnie te kolory,
       które wychodzą z templates/admin/layout.html.twig i z klas Tailwinda
       używanych w widokach modułów. Komentarz przy każdej mówi, skąd pochodzi,
       żeby przy następnej zmianie panelu było wiadomo, co zaktualizować. */
    --d-nav:     #141414;  /* #sidebar background */
    --d-nav-tx:  #acb0b9;  /* .nav-item color */
    --d-nav-mut: #4b5563;  /* .nav-group-label (podgrupa w module) */
    --d-nav-sec: #374151;  /* .nav-label (nagłówek sekcji sidebaru) */
    --d-nav-acc: #acdff6;  /* .nav-item.active — pasek, ikona, poświata */
    --d-ink:     #1f2937;  /* text-gray-800 — tytuł ekranu */
    --d-ink-2:   #374151;  /* text-gray-700 — treść komórek */
    --d-ink-3:   #6b7280;  /* text-gray-500 — opisy, wartości drugorzędne */
    --d-ink-4:   #9ca3af;  /* text-gray-400 — metadane, puste wartości */
    --d-ink-5:   #4b5563;  /* text-gray-600 — nagłówki tabel, etykiety pigułek */
    --d-line:    #e5e7eb;  /* border-gray-200 — ramka kart i tabel */
    --d-line-2:  #f3f4f6;  /* divide-gray-100 — linie między wierszami */
    --d-line-3:  #d1d5db;  /* border-gray-300 — pola formularza, przyciski wtórne */
    --d-head:    #f9fafb;  /* bg-gray-50 — nagłówek tabeli, tło strony panelu */
    --d-btn:     #111827;  /* bg-gray-900 — przycisk główny i aktywna pigułka */
    --d-focus:   #9ca3af;  /* ring-gray-400 — obwódka pola z fokusem */
    --d-bg:      #f9fafb;  /* <body class="bg-gray-50"> */
    --d-brand:   #4791b2;  /* znak ANOVEI — zostaje, to marka, nie panel */

    /* Panel deklaruje `font-family: 'Inter', system-ui, sans-serif` i NIE ładuje
       webfontu Inter — na maszynie bez niego renderuje się krojem systemowym.
       Replika robi dokładnie to samo: ten sam stos, ta sama klatka końcowa.
       (Wcześniej było tu 'Open Sans' ze strony — inna szerokość znaków, przez co
       ta sama tabela łamała się w innych miejscach niż w panelu.) */
    font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
    color: var(--d-ink);
    line-height: 1.35;
    -webkit-font-smoothing: antialiased;
}
@container (max-width: 760px) { .anv-stage { aspect-ratio: 16 / 11; } }
@container (max-width: 520px) { .anv-stage { aspect-ratio: 4 / 5; } }

/* Sceny leżą jedna na drugiej — przełączenie rozdziału to przenikanie,
   nie przeładowanie DOM-u (żadnego mignięcia bielą). */
.anv-scene {
    position: absolute;
    inset: 0;
    opacity: 0;
    visibility: hidden;
    transform: scale(1.015);
    transition: opacity .42s ease, transform .42s ease, visibility 0s linear .42s;
}
.anv-scene.is-active {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: opacity .42s ease, transform .42s ease, visibility 0s;
}

/* Warstwa zoomu — skaluje treść sceny wokół wskazanego elementu. Kursor siedzi
   WEWNĄTRZ niej: silnik liczy pozycje w układzie warstwy (dzieląc przez bieżącą
   skalę), więc zoom ich nie przesuwa, a sam glif jest kontrskalowany. */
.anv-zoom {
    position: absolute;
    inset: 0;
    display: flex;
    transform: scale(var(--anv-scale, 1));
    transform-origin: var(--anv-ox, 50%) var(--anv-oy, 50%);
    transition: transform .85s cubic-bezier(.4, 0, .2, 1);
    will-change: transform;
}

/* ── Replika: nawigacja boczna ───────────────────────────────────────────── */
.anv-nav {
    flex: 0 0 calc(var(--u) * 15.5);
    display: flex;
    flex-direction: column;
    background: var(--d-nav);
    color: var(--d-nav-tx);
    padding-bottom: calc(var(--u) * .6);
    overflow: hidden;
}
/* Menu boczne znika dopiero na wąskim ekranie, nie razem z kolumnami tabeli.
   Wcześniej próg był ten sam (760 px) i scena „jeden panel, kilka działów"
   w kolumnie obrazka klikała w menu, którego nie było widać — kursor jechał
   wtedy do lewego górnego rogu, bo ukryty element nie ma geometrii. */
@container (max-width: 520px) { .anv-nav { display: none; } }

/* ── Blok marki ──
   W panelu to <a class="px-5 py-3 bg-black/25 flex items-center gap-2"> z
   obrazkiem `h-8 w-8` (32×32) i napisem złożonym z dwóch członów:
     „ANOVEI"  — text-lg (18 px), font-bold, tracking-wide (.025em), biały
     „ SYSTEM" — text-sm (14 px), font-light, tracking-widest (.1em), gray-400
   Wymiary niżej to te wartości przemnożone przez stałą skalowania sceny
   (u = 13,5 px wobec 16 px bazy panelu), stąd 1,12u zamiast „na oko".

   ZNAK jest przezroczysty, nie jest kaflem. Sprawdzone na pliku, który panel
   naprawdę ładuje: 128×128, 76% pikseli w pełni przezroczystych, dominanta
   #52accb — czyli samo niebieskie „A" bez tła. Wcześniej replika miała tu biały
   glif na wypełnionym niebieskim kwadracie, czyli dokładną odwrotność: znacznie
   cięższą plamę, która przykuwała wzrok mocniej niż cokolwiek w panelu.
   Glif zajmuje ~69% wysokości pola obrazka — stąd zapas w viewBox znaku. */
.anv-nav__brand {
    display: flex;
    align-items: center;
    gap: calc(var(--u) * .5);
    padding: calc(var(--u) * .75) calc(var(--u) * 1.25);
    background: rgb(0 0 0 / 25%);
}
.anv-nav__mark {
    width: calc(var(--u) * 2);
    height: calc(var(--u) * 2);
    flex: 0 0 auto;
    color: #52accb;
}
.anv-nav__brand b {
    font-size: calc(var(--u) * 1.12);
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: .025em;
    color: #fff;
}
.anv-nav__brand span {
    font-size: calc(var(--u) * .87);
    font-weight: 300;
    line-height: 1.25;
    letter-spacing: .1em;
    color: var(--d-ink-4);
}

/* Nagłówek sekcji: w panelu to <button> z kreską nad sobą i chevronem
   (sekcje da się zwijać, stan siedzi w localStorage). Chevron jest częścią
   obrazu ekranu, więc replika go pokazuje — bez niego menu wygląda na płaską
   listę, a panel ma menu dwupoziomowe. */
.anv-nav__sec {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: calc(var(--u) * .95) calc(var(--u) * .25) calc(var(--u) * .2);
    padding: calc(var(--u) * .12) calc(var(--u) * .9) calc(var(--u) * .12) calc(var(--u) * .95);
    border-top: 1px solid rgb(255 255 255 / 5%);
    padding-top: calc(var(--u) * .75);
}
.anv-nav__sec b {
    font-size: calc(var(--u) * .72);
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--d-nav-sec);
}
.anv-nav__sec i { font-size: calc(var(--u) * .58); color: var(--d-nav-sec); }
/* Podgrupa wewnątrz modułu (.nav-group-label) — jaśniejsza i bez kreski. */
.anv-nav__group {
    padding: calc(var(--u) * .6) calc(var(--u) * .95) calc(var(--u) * .15);
    font-size: calc(var(--u) * .72);
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--d-nav-mut);
}
.anv-nav__item {
    position: relative;
    display: flex;
    align-items: center;
    gap: calc(var(--u) * .72);
    margin: 0 calc(var(--u) * .25);
    padding: calc(var(--u) * .55) calc(var(--u) * .9);
    border-radius: calc(var(--u) * .58);
    font-size: calc(var(--u) * .97);
    font-weight: 500;
    color: var(--d-nav-tx);
    transition: background-color .25s ease, color .25s ease;
}
.anv-nav__item i { width: calc(var(--u) * 1.3); text-align: center; font-size: .95em; }
.anv-nav__item.is-hot { background: rgb(255 255 255 / 7%); color: #e5e7eb; }
/* Aktywna pozycja panelu: tło rgba(172,223,246,.1), biały tekst, ikona
   w kolorze akcentu i pasek `box-shadow: inset 3px 0 0`. Pasek odtwarzamy
   pseudoelementem, bo ma dojechać animacją (kierunek przejścia), a cienia
   nie da się animować bez rozmycia krawędzi. */
.anv-nav__item.is-current { background: rgb(172 223 246 / 10%); color: #fff; font-weight: 600; }
.anv-nav__item.is-current i { color: var(--d-nav-acc); }
.anv-nav__item::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: calc(var(--u) * .24);
    background: var(--d-nav-acc);
    transform: scaleY(0);
    transition: transform .3s cubic-bezier(.4, 0, .2, 1);
}
.anv-nav__item.is-current::before { transform: scaleY(1); }
.anv-nav__spacer { flex: 1 1 auto; }
/* Stopka menu — „Wyloguj się" nad kreską, czerwone. W panelu jest zawsze na
   dole sidebaru, więc bez niej kolumna kończy się w powietrzu. */
.anv-nav__out {
    display: flex;
    align-items: center;
    gap: calc(var(--u) * .72);
    margin: 0 calc(var(--u) * .25);
    padding: calc(var(--u) * .55) calc(var(--u) * .9);
    border-top: 1px solid rgb(255 255 255 / 10%);
    padding-top: calc(var(--u) * .8);
    font-size: calc(var(--u) * .97);
    font-weight: 500;
    color: #f87171;
}
.anv-nav__out i { width: calc(var(--u) * 1.3); text-align: center; font-size: .95em; }

/* ── Replika: obszar treści ──────────────────────────────────────────────── */
.anv-main {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    background: var(--d-bg);
}
/* Pasek górny panelu: biały, przyklejony, z cienką kreską i delikatnym cieniem
   (`sticky top-0 bg-white border-b border-gray-200 shadow-sm`). Wysokość bierze
   się z 56-pikselowych slotów tytułu i narzędzi. */
.anv-top {
    display: flex;
    align-items: center;
    gap: calc(var(--u) * .9);
    padding: calc(var(--u) * .55) calc(var(--u) * 1.35);
    background: #fff;
    border-bottom: 1px solid var(--d-line);
    box-shadow: 0 1px 2px rgb(0 0 0 / 5%);
}
/* Kolumna, nie zwykły blok: tytuł i podtytuł bywają opakowane w .anv-swap
   (podmiana przy zmianie widoku), a inline-flex bez tego stanąłby w jednej linii. */
.anv-top__title { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; min-height: calc(var(--u) * 3.4); }
.anv-top__title b {
    display: block;
    font-size: calc(var(--u) * 1.3);
    font-weight: 700;
    line-height: 1.2;
    color: var(--d-ink);
    white-space: nowrap;
}
/* Okruszki: 11,5 px, szare, przecinane chevronem 7 px; ostatni człon ciemniejszy
   i pogrubiony (.crumb-current). Bez nich pasek tytułu wygląda jak nagłówek
   strony marketingowej, a nie jak panel z nawigacją kontekstową. */
.anv-top__crumbs {
    display: flex;
    align-items: center;
    gap: calc(var(--u) * .42);
    margin-top: 1px;
    font-size: calc(var(--u) * .8);
    color: var(--d-ink-4);
    white-space: nowrap;
}
.anv-top__crumbs i { font-size: calc(var(--u) * .48); opacity: .5; }
/* `font-size: inherit` jest tu konieczne, nie ozdobne: selektor `.anv-top__title b`
   wyżej łapie także <b> wewnątrz okruszków (to jego potomek) i ma tę samą wagę,
   więc bez tej deklaracji ostatni człon ścieżki renderował się w rozmiarze
   tytułu ekranu — trzy razy za duży. */
.anv-top__crumbs b { font-size: inherit; color: var(--d-ink-5); font-weight: 600; }

/* Warianty tego samego miejsca w interfejsie (tytuł ekranu, akcje w pasku
   górnym) przy przejściu między STRONAMI. Wymieniane przez `display`, nie
   przenikane: dwa nachodzące na siebie tytuły czytają się jak usterka renderu,
   a nie jak nawigacja. Wymiana nie koliduje z ogólną regułą [data-when] niżej,
   bo tamta rusza wyłącznie krycie i przesunięcie. */
.anv-alt[data-when] { display: none; }
.anv-alt[data-when].is-on { display: flex; }
.anv-top__title span {
    display: block;
    font-size: calc(var(--u) * .78);
    color: var(--d-ink-3);
}
.anv-top__right { margin-left: auto; display: flex; align-items: center; gap: calc(var(--u) * .55); }
/* Narzędzia panelu to kwadraty 44×44 z zaokrągleniem — nie same glify. */
.anv-top__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: calc(var(--u) * 2.4);
    height: calc(var(--u) * 2.4);
    border-radius: calc(var(--u) * .58);
    color: var(--d-ink-5);
    font-size: calc(var(--u) * 1.02);
}
@container (max-width: 520px) { .anv-top__icon { display: none; } }

.anv-body {
    flex: 1 1 auto;
    min-height: 0;
    padding: calc(var(--u) * 1.15) calc(var(--u) * 1.4);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

/* ── Replika: elementy sterujące ─────────────────────────────────────────── */
.anv-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: calc(var(--u) * .45);
    padding: calc(var(--u) * .55) calc(var(--u) * 1.05);
    border-radius: calc(var(--u) * .58);
    background: var(--d-btn);
    color: #fff;
    font-size: calc(var(--u) * .9);
    font-weight: 500;
    white-space: nowrap;
    transition: transform .12s ease, box-shadow .2s ease, filter .2s ease;
}
/* Wtórny przycisk panelu (`bg-white border-gray-300 text-gray-700`) — tak wygląda
   „Kalendarz" w nagłówku i „Szczegóły" w wierszu tabeli. */
.anv-btn--ghost { background: #fff; color: var(--d-ink-2); border: 1px solid var(--d-line-3); }
.anv-btn--sm { padding: calc(var(--u) * .45) calc(var(--u) * .85); font-size: calc(var(--u) * .86); }
/* Trzeci wariant: goły link w pasku akcji („← Wróć" na formularzu). */
.anv-btn--link { background: none; color: var(--d-ink-3); font-weight: 400; padding: calc(var(--u) * .4) 0; }
.anv-btn--brand { background: var(--d-brand); }
/* „Karta kontroli technicznej" na karcie zlecenia — jedyny pomarańczowy przycisk
   w panelu (`bg-orange-500`), bo to akcja, po którą serwisant sięga najczęściej. */
.anv-btn--orange { background: #f97316; }
/* Fokus w panelu to szary pierścień (`ring-gray-400`), nie niebieska poświata —
   to jeden z tych detali, po których od razu widać, czy makieta była robiona
   z panelu, czy z pamięci. */
.anv-btn.is-hot { box-shadow: 0 0 0 calc(var(--u) * .18) rgb(156 163 175 / 45%); filter: brightness(1.35); }
.anv-btn--ghost.is-hot { filter: none; background: #f3f4f6; }
.anv-btn.is-press { transform: scale(.955); }
/* Stan „zapisuję" — etykieta ustępuje miejsca spinnerowi. Bez tej klatki
   zapis wygląda na natychmiastowy, czyli na atrapę. */
.anv-btn__spin { display: none; width: calc(var(--u) * .85); height: calc(var(--u) * .85); animation: anv-spin .8s linear infinite; }
.anv-btn.is-busy .anv-btn__spin { display: block; }
.anv-btn.is-busy .anv-btn__ico { display: none; }
@keyframes anv-spin { to { transform: rotate(360deg); } }

/* Filtr statusu: w panelu to zwykłe linki `min-h-11 px-3 py-1.5 rounded-lg`,
   bieżący na czarno (`bg-gray-900 text-white`), reszta biała z ramką gray-300. */
.anv-pill {
    display: inline-flex;
    align-items: center;
    padding: calc(var(--u) * .42) calc(var(--u) * .82);
    border: 1px solid var(--d-line-3);
    border-radius: calc(var(--u) * .58);
    background: #fff;
    font-size: calc(var(--u) * .88);
    color: var(--d-ink-5);
    white-space: nowrap;
    transition: background-color .25s ease, color .25s ease, border-color .25s ease, transform .12s ease, box-shadow .2s ease;
}
.anv-pill.is-current { background: var(--d-btn); border-color: var(--d-btn); color: #fff; }
.anv-pill.is-hot { background: #f9fafb; box-shadow: 0 0 0 calc(var(--u) * .16) rgb(156 163 175 / 35%); }
.anv-pill.is-current.is-hot { background: var(--d-btn); }
.anv-pill.is-press { transform: scale(.96); }

.anv-input {
    display: flex;
    align-items: center;
    gap: calc(var(--u) * .45);
    padding: calc(var(--u) * .5) calc(var(--u) * .75);
    border: 1px solid var(--d-line-3);
    border-radius: calc(var(--u) * .58);
    background: #fff;
    font-size: calc(var(--u) * .88);
    color: var(--d-ink-4);
    min-width: 0;
    transition: border-color .2s ease, box-shadow .2s ease;
}
.anv-input i { font-size: .82em; color: var(--d-ink-4); flex: 0 0 auto; }
.anv-input span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
/* `focus:ring-2 focus:ring-gray-400` — szary pierścień, jak w panelu.
   Wypełnione pole dostaje ciemną ramkę (`border-gray-900`), bo tak widok listy
   oznacza aktywne kryterium wyszukiwania. */
.anv-input.is-hot, .anv-input.is-typing { border-color: var(--d-focus); box-shadow: 0 0 0 calc(var(--u) * .18) rgb(156 163 175 / 40%); }
.anv-input.is-filled { border-color: var(--d-btn); }
/* Wpisywany tekst dostaje kolor treści (nie podpowiedzi) i karetkę. */
.anv-input.is-typing span, .anv-input.is-filled span { color: var(--d-ink); }
.anv-input.is-typing span::after {
    content: "";
    display: inline-block;
    width: 1px;
    height: calc(var(--u) * .95);
    margin-left: 1px;
    vertical-align: -2px;
    background: var(--d-ink);
    animation: anv-caret .9s steps(1) infinite;
}
@keyframes anv-caret { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }

/* ── Replika: karty KPI ──────────────────────────────────────────────────── */
.anv-kpis { display: flex; gap: calc(var(--u) * .7); margin-bottom: calc(var(--u) * 1.05); flex: 0 0 auto; }
.anv-kpi {
    flex: 1 1 0;
    min-width: 0;
    padding: calc(var(--u) * .7) calc(var(--u) * .5);
    border-radius: calc(var(--u) * .85);
    border: 1px solid transparent;
    text-align: center;
    transition: transform .35s cubic-bezier(.2, .8, .2, 1), box-shadow .35s ease;
}
.anv-kpi b { display: block; font-size: calc(var(--u) * 1.45); font-weight: 700; line-height: 1.2; font-variant-numeric: tabular-nums; }
/* W panelu etykieta dziedziczy kolor kafla (`bg-blue-50 text-blue-700`), a nie
   jest szara — kafle statusów są kolorowe w całości i to one niosą kod barwny
   powtórzony niżej w plakietkach tabeli. */
.anv-kpi span { display: block; font-size: calc(var(--u) * .78); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.anv-kpi--blue   { background: #eff6ff; border-color: #bfdbfe; color: #1d4ed8; }
.anv-kpi--orange { background: #fff7ed; border-color: #fed7aa; color: #c2410c; }
.anv-kpi--yellow { background: #fefce8; border-color: #fef08a; color: #a16207; }
.anv-kpi--purple { background: #f5f3ff; border-color: #ddd6fe; color: #6d28d9; }
.anv-kpi--green  { background: #f0fdf4; border-color: #bbf7d0; color: #15803d; }
.anv-kpi--gray   { background: #f9fafb; border-color: #e5e7eb; color: #6b7280; }
/* Rezultat akcji: karta, której liczba się zmieniła, unosi się na moment. */
.anv-kpi.is-bumped { transform: translateY(calc(var(--u) * -.28)); box-shadow: 0 calc(var(--u) * .6) calc(var(--u) * 1.4) rgb(15 27 38 / 12%); }
@container (max-width: 760px) { .anv-kpi:nth-child(n + 5) { display: none; } }
@container (max-width: 520px) { .anv-kpi:nth-child(n + 4) { display: none; } }

/* ── Replika: karty i tabele ─────────────────────────────────────────────── */
.anv-card {
    background: #fff;
    border: 1px solid var(--d-line);
    border-radius: calc(var(--u) * .85);  /* rounded-xl */
    overflow: hidden;
}
.anv-card__head {
    padding: calc(var(--u) * .7) calc(var(--u) * .95);
    border-bottom: 1px solid var(--d-line-2);
    font-size: calc(var(--u) * .74);
    font-weight: 700;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--d-ink-3);
}
/* Wiersz tabeli panelu: `px-5 py-3`, linia `divide-gray-100`, najazd `bg-gray-50`.
   `align-items: flex-start`, bo komórka „Numer / Maszyna" jest w panelu
   pięciowierszowa (numer, nazwa, marka+model, nr seryjny, klient) i pozostałe
   kolumny mają się z nią równać do GÓRY, a nie zawisnąć w połowie wysokości. */
.anv-row {
    display: flex;
    align-items: flex-start;
    gap: calc(var(--u) * .7);
    padding: calc(var(--u) * .62) calc(var(--u) * 1.05);
    border-top: 1px solid var(--d-line-2);
    font-size: calc(var(--u) * .9);
    color: var(--d-ink-3);
    transition: background-color .3s ease;
}
.anv-row:first-child { border-top: 0; }
.anv-row--head {
    align-items: center;
    border-top: 0;
    border-bottom: 1px solid var(--d-line);
    font-size: calc(var(--u) * .88);
    font-weight: 600;
    color: var(--d-ink-5);
    background: var(--d-head);
}
.anv-row.is-hot { background: var(--d-head); }
.anv-row__c { min-width: 0; flex: 1 1 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.anv-row__c--w2 { flex: 2 1 0; }
.anv-row__c--num { flex: 0 0 auto; text-align: right; font-variant-numeric: tabular-nums; }
.anv-row__c--act { flex: 0 0 auto; text-align: right; }
.anv-row__t { display: block; font-weight: 700; color: var(--d-ink); }
.anv-row__s { display: block; font-size: calc(var(--u) * .74); color: var(--d-ink-3); }

/* ── Komórka wielowierszowa (kolumna „Numer / Maszyna") ──
   Kolejność i kolory jak w modules/Serwis/templates/index.html.twig:
     numer         — mono, xs, gray-500
     nazwa maszyny — medium, gray-700
     marka + model — xs, gray-400
     nr seryjny    — mono, xs, gray-400, z ikoną kodu kreskowego
     klient        — xs, gray-500
   Wcześniej replika ściskała to w dwie linie („nazwa" + „numer · VIN"), przez co
   wiersz był o połowę niższy niż w panelu i na ekran wchodziło dwa razy więcej
   zleceń niż naprawdę. */
.anv-cell { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.anv-cell > * { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.anv-cell__num  { font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace; font-size: calc(var(--u) * .76); font-weight: 500; color: var(--d-ink-3); }
.anv-cell__name { font-size: calc(var(--u) * .9); font-weight: 500; color: var(--d-ink-2); }
.anv-cell__sub  { font-size: calc(var(--u) * .76); color: var(--d-ink-4); }
.anv-cell__vin  { font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace; font-size: calc(var(--u) * .76); color: var(--d-ink-4); }
.anv-cell__vin i { font-size: .85em; margin-right: calc(var(--u) * .22); }
.anv-cell__cli  { font-size: calc(var(--u) * .76); color: var(--d-ink-3); }

/* Ikona sortowania w nagłówku: nieaktywna kolumna ma bladą strzałkę dwustronną,
   aktywna — kierunkową w kolorze tekstu. To jedyny element nagłówka, po którym
   widać, że tabela panelu jest sortowalna. */
.anv-sort { display: inline-flex; align-items: center; gap: calc(var(--u) * .3); }
.anv-sort i { font-size: calc(var(--u) * .62); color: var(--d-line-3); }
.anv-sort.is-active { color: var(--d-ink); }
.anv-sort.is-active i { color: var(--d-ink); }

/* Notatka „na czym stoimy" pod plakietką statusu — cecha panelu, której żaden
   konkurencyjny zrzut nie ma, więc tym bardziej ma być widoczna. */
.anv-note {
    display: flex;
    gap: calc(var(--u) * .3);
    margin-top: calc(var(--u) * .3);
    font-size: calc(var(--u) * .76);
    color: var(--d-ink-3);
    white-space: normal;
    line-height: 1.3;
}
.anv-note i { color: var(--d-line-3); font-size: .85em; margin-top: calc(var(--u) * .16); flex: 0 0 auto; }

/* Podsumowanie pod filtrami („Znaleziono 8 zleceń") — panel pokazuje je zawsze,
   bo bez niego nie wiadomo, czy lista jest pełna, czy już zawężona. */
.anv-found { font-size: calc(var(--u) * .78); color: var(--d-ink-3); margin-top: calc(var(--u) * .45); flex: 0 0 auto; }
.anv-found b { font-weight: 700; color: var(--d-ink-2); }

.anv-mono { font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace; font-size: calc(var(--u) * .72); color: #94a3b8; }
@container (max-width: 760px) { .anv-hide-md { display: none !important; } }
@container (max-width: 520px) { .anv-hide-sm { display: none !important; } }

.anv-badge {
    display: inline-flex;
    align-items: center;
    gap: calc(var(--u) * .28);
    padding: calc(var(--u) * .16) calc(var(--u) * .58);
    border-radius: 999px;
    font-size: calc(var(--u) * .78);
    font-weight: 500;
    white-space: nowrap;
    transition: background-color .4s ease, color .4s ease;
}
/* Kod barwny statusów zlecenia — 1:1 z mapą `statusBadge` w widoku listy
   (modules/Serwis/templates/index.html.twig, wiersz 22). */
.anv-badge--blue   { background: #dbeafe; color: #1d4ed8; }  /* nowe */
.anv-badge--orange { background: #ffedd5; color: #c2410c; }  /* w_realizacji */
.anv-badge--yellow { background: #fef9c3; color: #a16207; }  /* oczekuje_na_czesci */
.anv-badge--purple { background: #ede9fe; color: #6d28d9; }  /* do_rozliczenia */
.anv-badge--green  { background: #dcfce7; color: #15803d; }  /* zakonczone */
.anv-badge--gray   { background: #f3f4f6; color: #9ca3af; }  /* anulowane */
/* Rodzaj serwisu: typy gwarancyjne dostają bursztynową plakietkę z tarczą,
   pozostałe są zwykłym szarym tekstem (patrz kolumna „Typ"). */
.anv-badge--amber  { background: #fef3c7; color: #92400e; }
.anv-badge--red    { background: #fee2e2; color: #b91c1c; }

.anv-link { color: var(--d-brand); font-weight: 600; white-space: nowrap; }

/* Podmiana etykiety w miejscu — stary i nowy status leżą na sobie, więc
   zmiana „Poniżej minimum" → „W drodze" nie rozpycha kolumny tabeli. */
.anv-swap { position: relative; display: inline-flex; }
.anv-swap > :nth-child(n + 2) { position: absolute; left: 0; top: 0; }

/* Przełączanie widoków (lista → karta, pulpit → moduł). Widoki leżą na sobie
   i przenikają z lekkim przesunięciem w bok — ruch niesie kierunek nawigacji,
   dzięki czemu widać, że to przejście, a nie przeładowanie. */
.anv-views { position: relative; flex: 1 1 auto; min-height: 0; }
.anv-view {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    min-height: 0;
}
/* Przejście z zakładką czasową, nie zwykłe przenikanie: dwie gęste tabele
   nałożone na siebie przy 50% krycia czytają się jak błąd renderu. Widok
   wychodzący gaśnie szybko (.16 s), wchodzący zapala się dopiero po nim
   (opóźnienie .18 s) — w środku przejścia na ekranie jest jeden widok.

   KRYCIE I PRZESUNIĘCIE MUSZĄ BYĆ TUTAJ, a nie brane z ogólnej reguły
   [data-when] na końcu arkusza. Tamta ma dziewięć `:not()`, czyli wagę dziesięciu
   klas, i wygrywała z `.anv-view[data-when]` (waga dwóch) — nadpisywała i wjazd
   z boku, i zakładkę czasową. Efekt był dokładnie taki, przed jakim ostrzega
   akapit wyżej: dwa widoki przenikały się jednocześnie przez 0,4 s. Dlatego
   `.anv-view` jest wyjęty z tamtej reguły i deklaruje oba stany sam. */
.anv-view[data-when] {
    opacity: 0;
    transform: translateX(calc(var(--u) * 1.4));
    transition: opacity .16s ease, transform .34s cubic-bezier(.2, .8, .2, 1);
}
.anv-view[data-when][data-anim="back"] { transform: translateX(calc(var(--u) * -1.4)); }
.anv-view[data-when].is-on {
    opacity: 1;
    transform: none;
    transition: opacity .22s ease .18s, transform .38s cubic-bezier(.2, .8, .2, 1) .12s;
}

/* ── Replika: formularz na osobnej stronie ───────────────────────────────── */
/* Panel nie ma dla zleceń modala — „Nowe zlecenie" to <a> na /admin/serwis/nowe,
   czyli PEŁNA STRONA (modules/Serwis/module.php:46). Formularz siedzi w kolumnie
   `max-w-2xl`, wyrównanej do lewej, więc po jego prawej stronie zostaje szare tło
   panelu. To charakterystyczny widok i replika ma go trzymać. */
.anv-form { max-width: calc(var(--u) * 42); }
.anv-fcard {
    background: #fff;
    border: 1px solid var(--d-line);
    border-radius: calc(var(--u) * .85);
    padding: calc(var(--u) * 1.5);
    display: flex;
    flex-direction: column;
    gap: calc(var(--u) * 1.25);   /* space-y-5 */
}
.anv-flabel { display: block; font-size: calc(var(--u) * .87); font-weight: 500; color: var(--d-ink-2); margin-bottom: calc(var(--u) * .25); }
.anv-flabel s { color: #ef4444; text-decoration: none; }   /* gwiazdka pola wymaganego */
.anv-fhint { font-size: calc(var(--u) * .76); color: var(--d-ink-3); margin-top: calc(var(--u) * .25); }
.anv-fgrid { display: grid; grid-template-columns: 1fr 1fr; gap: calc(var(--u) * 1); }
/* Zakładki kategorii nad listą maszyn — zawężają wybór, żeby nie przewijać
   przez dziesiątki pozycji. */
.anv-ftabs { display: flex; flex-wrap: wrap; gap: calc(var(--u) * .35); margin-bottom: calc(var(--u) * .4); }
.anv-ftab {
    padding: calc(var(--u) * .22) calc(var(--u) * .7);
    border: 1px solid var(--d-line-3);
    border-radius: 999px;
    background: #fff;
    font-size: calc(var(--u) * .76);
    color: var(--d-ink-2);
    white-space: nowrap;
}
.anv-ftab.is-current { background: var(--d-btn); border-color: var(--d-btn); color: #fff; }
/* Pole wyboru: wartość pojawia się w całości, bo użytkownik ją WYBIERA z listy,
   a nie wpisuje. Migająca karetka maszynopisu byłaby tu kłamstwem o interakcji. */
.anv-input--select.is-typing span::after { display: none; }
.anv-input--select i:last-child { margin-left: auto; }

/* Pasek zapisu przyklejony do dołu ekranu (`.admin-sticky-actions` z layoutu):
   „Zapisz" jest widoczny bez przewijania długiego formularza. */
.anv-sticky {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(var(--u) * .75);
    display: flex;
    align-items: center;
    gap: calc(var(--u) * .75);
    padding: calc(var(--u) * .75);
    border: 1px solid var(--d-line);
    border-radius: calc(var(--u) * .75);
    background: rgb(255 255 255 / 95%);
    box-shadow: 0 calc(var(--u) * -.3) calc(var(--u) * 1.2) rgb(0 0 0 / 6%);
}

/* ── Replika: karta zlecenia (widok po zapisie) ──────────────────────────── */
/* store() przekierowuje na /admin/serwis/{id} (SerwisController.php:210), więc
   po zapisie użytkownik ląduje TUTAJ, a nie z powrotem na liście. Bez flasha —
   kontroler go nie ustawia; potwierdzeniem jest sam nadany numer w tytule. */
.anv-sbar { display: flex; flex-wrap: wrap; align-items: center; gap: calc(var(--u) * .35); }
.anv-sbar > span:first-child { font-size: calc(var(--u) * .78); color: var(--d-ink-4); margin-right: calc(var(--u) * .25); }
.anv-sbtn {
    display: inline-flex;
    align-items: center;
    gap: calc(var(--u) * .35);
    padding: calc(var(--u) * .5) calc(var(--u) * .9);
    border: 1px solid var(--d-line);
    border-radius: calc(var(--u) * .58);
    background: var(--d-head);
    font-size: calc(var(--u) * .85);
    font-weight: 500;
    color: var(--d-ink-3);
    white-space: nowrap;
}
/* Status bieżący jest wciśnięty na czarno — zmiana statusu to jedno kliknięcie,
   bez wchodzenia w „Edytuj". */
.anv-sbtn.is-current { background: var(--d-btn); border-color: var(--d-btn); color: #fff; font-weight: 600; }
.anv-dl { display: grid; grid-template-columns: 1fr 1fr; gap: calc(var(--u) * .8) calc(var(--u) * 1.5); font-size: calc(var(--u) * .88); }
.anv-dl dt, .anv-dl .dt { display: block; color: var(--d-ink-4); }
.anv-dl dd, .anv-dl .dd { display: block; color: var(--d-ink); }

/* ── Replika: kafle wykresów (ECharts) ───────────────────────────────────── */
/* Widok listy zleceń ma nad tabelą dwa wykresy w kartach `rounded-xl p-4`:
   pierścień „Rozkład statusów zleceń" i słupki „Zlecenia wg typu (ostatnie
   3 m.)". Poprzednia makieta ich nie miała w ogóle — a to one, obok kolorowych
   kafli statusów, robią pierwsze wrażenie na tym ekranie. Odtwarzamy je
   wektorem, nie biblioteką: 30 kB ECharts na animację dekoracyjną to zły
   interes, a SVG jest ostry na każdym ekranie.

   Kolory pierścienia = ZlecenieModel::$statusColors (Models/ZlecenieModel.php
   :461). Słupki = panelChartInk.neutralBar (#252525), etykiety osi #9ca3af. */
.anv-charts {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: calc(var(--u) * .8);
    margin-bottom: calc(var(--u) * 1.1);
    flex: 0 0 auto;
}
@container (max-width: 760px) { .anv-charts { grid-template-columns: 1fr; } .anv-charts > :nth-child(2) { display: none; } }
.anv-chart {
    background: #fff;
    border: 1px solid var(--d-line);
    border-radius: calc(var(--u) * .85);
    padding: calc(var(--u) * .75) calc(var(--u) * .85);
    min-width: 0;
    /* ECharts rysuje w <canvas>/<svg> o zadanym rozmiarze i nigdy nie wychodzi
       poza swój kafel. Kafel repliki ma zachować się tak samo — to zabezpieczenie
       na wypadek, gdyby podpis osi kiedyś urósł ponad zarezerwowane miejsce. */
    overflow: hidden;
}
.anv-chart__t {
    font-size: calc(var(--u) * .72);
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--d-ink-5);
    margin-bottom: calc(var(--u) * .55);
}
/* 13,7u ≈ 185 px = 220 px z panelu przeskalowane tą samą stałą co reszta sceny
   (u = 13,5 px wobec 16 px bazy panelu). Kusi, żeby dać mniej i zmieścić więcej
   wierszy tabeli — ale wtedy scena kłamałaby o tym, ile miejsca ten ekran
   naprawdę oddaje wykresom, zanim serwisant zobaczy pierwsze zlecenie. */
.anv-chart__c { height: calc(var(--u) * 13.7); display: flex; align-items: center; gap: calc(var(--u) * .7); }
.anv-chart__c svg { display: block; overflow: visible; }

/* Legenda pierścienia — w panelu stoi po lewej, pionowo, 11 px. */
.anv-legend { display: flex; flex-direction: column; gap: calc(var(--u) * .3); font-size: calc(var(--u) * .72); color: #666; min-width: 0; }
.anv-legend div { display: flex; align-items: center; gap: calc(var(--u) * .3); white-space: nowrap; }
.anv-legend i { width: calc(var(--u) * .55); height: calc(var(--u) * .55); border-radius: 2px; flex: 0 0 auto; }

/* Pierścień: jeden okrąg na segment, rysowany obwodem kreski. Przeliczenie
   scenariusza (zawężenie listy) animuje `stroke-dasharray`, więc wykres
   „przelicza się" tak jak ECharts po odświeżeniu danych. */
.anv-donut circle { fill: none; stroke-width: 11; transition: stroke-dasharray .9s cubic-bezier(.4, 0, .2, 1), stroke-dashoffset .9s cubic-bezier(.4, 0, .2, 1); }

/* Słupki wykresu typów — etykiety osi X pochylone o 45°, jak w konfiguracji.
   `containLabel: true` w ECharts oddaje pochylonym podpisom sporą część
   wysokości kafla; tutaj robi to samo `padding-bottom` na obszarze słupków.
   `barMaxWidth: 40` z konfiguracji ogranicza szerokość słupka. */
.anv-cbars { display: flex; align-items: flex-end; justify-content: space-around; gap: calc(var(--u) * .3); height: 100%; flex: 1 1 auto; padding-bottom: calc(var(--u) * 4.9); }
.anv-cbar { flex: 1 1 0; position: relative; height: 100%; display: flex; align-items: flex-end; justify-content: center; }
.anv-cbar span {
    display: block;
    width: 100%;
    max-width: calc(var(--u) * 2.5);
    background: #252525;
    height: var(--h, 0%);
    transition: height .9s cubic-bezier(.4, 0, .2, 1);
}
/* Dwa komplety słupków — przed i po dołożeniu zlecenia. Podmiana przez `display`,
   nie przez krycie: dwa słupki jeden na drugim w tym samym miejscu siatki dałyby
   szarą zlepkę w trakcie przenikania. Wykres i tak przeładowuje się razem ze
   stroną, więc skok wartości jest tu prawdą, a nie uproszczeniem. */
.anv-cbar span[data-when] { display: none; }
.anv-cbar span[data-when].is-on { display: block; }
/* Podpis osi X: pochylony o 45°, zaczepiony w środku słupka i rozwijany
   w prawo-w-dół — tak samo jak `axisLabel: { rotate: 45 }` w ECharts.
   Rozmiar dobrany tak, żeby najdłuższy podpis („Gwarancja przegląd") zmieścił
   się w wysokości zarezerwowanej przez `padding-bottom` obszaru słupków:
   przy 45° podpis zjada 0,71 swojej długości w pionie. */
.anv-cbar b {
    position: absolute;
    top: 100%;
    left: 50%;
    font-size: calc(var(--u) * .58);
    font-weight: 400;
    line-height: 1.1;
    color: var(--d-ink-4);
    white-space: nowrap;
    transform-origin: left top;
    transform: rotate(45deg);
    margin-top: calc(var(--u) * .3);
}
.anv-caxis { width: 1px; align-self: stretch; background: var(--d-line); margin-bottom: calc(var(--u) * 4.9); }

/* ── Replika: wykresy ────────────────────────────────────────────────────── */
/* Słupki rosną wysokością przez transition, nie przez klatki kluczowe: restart
   sceny ma zawsze zaczynać od zera, bez czekania na koniec cyklu animacji. */
.anv-bars { display: flex; align-items: flex-end; gap: calc(var(--u) * .5); height: 100%; }
.anv-bar { flex: 1 1 0; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; height: 100%; gap: calc(var(--u) * .3); }
/* Tor słupka. Procentowa wysokość wypełnienia liczy się od TORU, nie od całej
   kolumny — inaczej słupek 96% zabierał miejsce etykiecie miesiąca i podpis
   pod wykresem był ścinany do niewidocznego paska. */
.anv-bar__track {
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    display: flex;
    align-items: flex-end;
    gap: calc(var(--u) * .18);
}
.anv-bar__lab { flex: 0 0 auto; }
.anv-bar__fill {
    width: 100%;
    height: 0;
    border-radius: calc(var(--u) * .2) calc(var(--u) * .2) 0 0;
    background: linear-gradient(180deg, #93c5fd, #3b82f6);
    transition: height .9s cubic-bezier(.2, .8, .2, 1);
}
.anv-bar__fill--amber { background: linear-gradient(180deg, #fcd34d, #f59e0b); }
.anv-bar__fill--green { background: linear-gradient(180deg, #86efac, #16a34a); }
.anv-bar__fill--slate { background: linear-gradient(180deg, #cbd5e1, #94a3b8); }
.anv-bar__fill--red   { background: linear-gradient(180deg, #fca5a5, #ef4444); }
.anv-bar.is-on .anv-bar__fill { height: var(--h, 50%); }
.anv-bar__lab { font-size: calc(var(--u) * .68); color: var(--d-ink-3); white-space: nowrap; }

/* Linia trendu — rysowana obwodem ścieżki. */
.anv-spark { width: 100%; height: 100%; overflow: visible; }
.anv-spark__line {
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 900;
    stroke-dashoffset: 900;
    transition: stroke-dashoffset 1.6s cubic-bezier(.4, 0, .2, 1);
}
.anv-spark__area { opacity: 0; transition: opacity .6s ease .55s; }
.anv-spark.is-on .anv-spark__line { stroke-dashoffset: 0; }
.anv-spark.is-on .anv-spark__area { opacity: 1; }

/* Pierścień statusów — ten sam mechanizm co linia. */
.anv-ring__seg { transition: stroke-dashoffset 1.1s cubic-bezier(.4, 0, .2, 1); stroke-dashoffset: var(--off-0, 400); }
.anv-ring.is-on .anv-ring__seg { stroke-dashoffset: var(--off, 0); }

/* ── Replika: kalendarz ──────────────────────────────────────────────────── */
.anv-cal { display: grid; grid-template-columns: repeat(7, 1fr); gap: calc(var(--u) * .22); flex: 1 1 auto; min-height: 0; }
.anv-cal__hd { font-size: calc(var(--u) * .72); font-weight: 700; color: var(--d-ink-3); text-align: center; padding-bottom: calc(var(--u) * .15); }
.anv-cal__d {
    display: flex;
    flex-direction: column;
    gap: calc(var(--u) * .18);
    padding: calc(var(--u) * .3);
    border: 1px solid var(--d-line-2);
    border-radius: calc(var(--u) * .3);
    background: #fff;
    font-size: calc(var(--u) * .7);
    color: var(--d-ink-3);
    min-height: 0;
    overflow: hidden;
    transition: background-color .3s ease, border-color .3s ease, box-shadow .3s ease;
}
.anv-cal__d.is-sel { border-color: var(--d-brand); background: #f0f7fa; box-shadow: inset 0 0 0 1px rgb(71 145 178 / 35%); }
.anv-ev {
    border-radius: calc(var(--u) * .2);
    padding: calc(var(--u) * .1) calc(var(--u) * .25);
    font-size: calc(var(--u) * .62);
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    background: #bbf7d0;
    color: #14532d;
}
.anv-ev--amber { background: #fef08a; color: #713f12; }
.anv-ev--new   { background: #bfdbfe; color: #1e3a8a; }

/* ── Replika: modal, szuflada, drugie okno, toast ────────────────────────── */
.anv-veil {
    position: absolute;
    inset: 0;
    background: rgb(9 14 22 / 42%);
    backdrop-filter: blur(2px);
    opacity: 0;
    visibility: hidden;
    transition: opacity .35s ease, visibility 0s linear .35s;
    z-index: 5;
}
.anv-veil.is-on { opacity: 1; visibility: visible; transition: opacity .35s ease, visibility 0s; }

.anv-modal {
    position: absolute;
    z-index: 6;
    left: 50%;
    top: 50%;
    width: min(calc(var(--u) * 34), 84%);
    background: #fff;
    border-radius: calc(var(--u) * .7);
    box-shadow: 0 calc(var(--u) * 2.2) calc(var(--u) * 4) rgb(9 14 22 / 32%);
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
    transform: translate(-50%, calc(-50% + var(--u) * 1.6)) scale(.97);
    transition: opacity .38s cubic-bezier(.2, .8, .2, 1), transform .38s cubic-bezier(.2, .8, .2, 1), visibility 0s linear .38s;
}
.anv-modal.is-on {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, -50%) scale(1);
    transition: opacity .38s cubic-bezier(.2, .8, .2, 1), transform .38s cubic-bezier(.2, .8, .2, 1), visibility 0s;
}
.anv-modal__head {
    display: flex;
    align-items: center;
    gap: calc(var(--u) * .5);
    padding: calc(var(--u) * .85) calc(var(--u) * 1.05);
    border-bottom: 1px solid var(--d-line);
    font-family: 'Montserrat', system-ui, sans-serif;
    font-size: calc(var(--u) * 1.05);
    color: var(--d-ink);
}
.anv-modal__head i { color: var(--d-brand); }
.anv-modal__body { padding: calc(var(--u) * .95) calc(var(--u) * 1.05); display: flex; flex-direction: column; gap: calc(var(--u) * .6); }
.anv-modal__foot {
    display: flex;
    justify-content: flex-end;
    gap: calc(var(--u) * .5);
    padding: calc(var(--u) * .8) calc(var(--u) * 1.05);
    background: #fbfcfd;
    border-top: 1px solid var(--d-line);
}
.anv-field { display: flex; flex-direction: column; gap: calc(var(--u) * .25); }
.anv-field > label { font-size: calc(var(--u) * .74); font-weight: 600; color: var(--d-ink-2); }
.anv-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: calc(var(--u) * .6); }

/* Szuflada — wjazd z prawej. Używana tam, gdzie modal zasłoniłby kontekst,
   a scenariusz opiera się właśnie na tym, że tło się zmienia (magazyn). */
.anv-drawer {
    position: absolute;
    z-index: 6;
    top: 0; right: 0; bottom: 0;
    width: min(calc(var(--u) * 26), 64%);
    background: #fff;
    border-left: 1px solid var(--d-line);
    box-shadow: calc(var(--u) * -1.2) 0 calc(var(--u) * 3) rgb(9 14 22 / 20%);
    display: flex;
    flex-direction: column;
    transform: translateX(101%);
    visibility: hidden;
    transition: transform .48s cubic-bezier(.32, .72, 0, 1), visibility 0s linear .48s;
}
.anv-drawer.is-on { transform: none; visibility: visible; transition: transform .48s cubic-bezier(.32, .72, 0, 1), visibility 0s; }

/* Drugie okno — publiczna część produktu (sklep z biletami) wjeżdżająca przed
   panel. Pokazuje stronę klienta i skutek jej działania po stronie obsługi. */
.anv-window2 {
    position: absolute;
    z-index: 6;
    right: calc(var(--u) * 1.2);
    bottom: calc(var(--u) * 1.2);
    width: min(calc(var(--u) * 25), 62%);
    border-radius: calc(var(--u) * .6);
    background: #fff;
    border: 1px solid var(--d-line);
    box-shadow: 0 calc(var(--u) * 2) calc(var(--u) * 3.6) rgb(9 14 22 / 30%);
    overflow: hidden;
    opacity: 0;
    visibility: hidden;
    transform: translate(calc(var(--u) * .8), calc(var(--u) * 1.4)) scale(.96);
    transform-origin: 100% 100%;
    transition: opacity .42s cubic-bezier(.2, .8, .2, 1), transform .42s cubic-bezier(.2, .8, .2, 1), visibility 0s linear .42s;
}
.anv-window2.is-on { opacity: 1; visibility: visible; transform: none; transition: opacity .42s cubic-bezier(.2, .8, .2, 1), transform .42s cubic-bezier(.2, .8, .2, 1), visibility 0s; }
.anv-window2__bar {
    display: flex;
    align-items: center;
    gap: calc(var(--u) * .35);
    padding: calc(var(--u) * .38) calc(var(--u) * .6);
    background: #f2f4f7;
    border-bottom: 1px solid var(--d-line);
    font-size: calc(var(--u) * .66);
    color: var(--d-ink-3);
}
.anv-window2__bar b { width: calc(var(--u) * .4); height: calc(var(--u) * .4); border-radius: 50%; background: #cbd2da; flex: 0 0 auto; }
.anv-window2__body { padding: calc(var(--u) * .7); display: flex; flex-direction: column; gap: calc(var(--u) * .45); }

.anv-toast {
    position: absolute;
    z-index: 7;
    right: calc(var(--u) * 1.2);
    top: calc(var(--u) * 1.2);
    display: flex;
    align-items: center;
    gap: calc(var(--u) * .55);
    max-width: calc(var(--u) * 24);
    padding: calc(var(--u) * .6) calc(var(--u) * .9);
    border-radius: calc(var(--u) * .5);
    background: #101922;
    color: #fff;
    font-size: calc(var(--u) * .85);
    box-shadow: 0 calc(var(--u) * 1.2) calc(var(--u) * 2.4) rgb(9 14 22 / 34%);
    opacity: 0;
    visibility: hidden;
    transform: translateY(calc(var(--u) * -.7));
    transition: opacity .35s cubic-bezier(.2, .8, .2, 1), transform .35s cubic-bezier(.2, .8, .2, 1), visibility 0s linear .35s;
}
.anv-toast.is-on { opacity: 1; visibility: visible; transform: none; transition: opacity .35s cubic-bezier(.2, .8, .2, 1), transform .35s cubic-bezier(.2, .8, .2, 1), visibility 0s; }
.anv-toast i { color: #4ade80; flex: 0 0 auto; }
.anv-toast--warn i { color: #fbbf24; }

/* ── Uniwersalne wejście elementów sterowanych stanem ────────────────────── */
/* Wszystko, co ma data-when, jest domyślnie schowane; silnik dokłada .is-on.
   Modal, szuflada, toast, okno, słupki i wykresy mają własne przejścia wyżej —
   reguła niżej ich nie dotyczy, żeby nie kasować transformów wjazdu. */
[data-when]:not(.anv-modal):not(.anv-drawer):not(.anv-toast):not(.anv-veil):not(.anv-window2):not(.anv-bar):not(.anv-spark):not(.anv-ring):not(.anv-scroll):not(.anv-view) {
    opacity: 0;
    transform: translateY(calc(var(--u) * .5));
    transition: opacity .4s cubic-bezier(.2, .8, .2, 1), transform .4s cubic-bezier(.2, .8, .2, 1);
}
[data-when]:not(.anv-modal):not(.anv-drawer):not(.anv-toast):not(.anv-veil):not(.anv-window2):not(.anv-bar):not(.anv-spark):not(.anv-ring):not(.anv-scroll):not(.anv-view).is-on {
    opacity: 1;
    transform: none;
}
/* Wariant „wiersz" — nowy rekord wjeżdża z lewej i błyska podświetleniem,
   bo to on jest rezultatem całego scenariusza. */
.anv-row[data-when] { transform: translateX(calc(var(--u) * -1)); }
.anv-row[data-when].is-on { animation: anv-row-in 1.8s ease-out; }
@keyframes anv-row-in {
    0%   { background-color: rgb(71 145 178 / 20%); }
    100% { background-color: transparent; }
}
/* Wariant „podmiana w miejscu" — liczby i etykiety, przy których przesunięcie
   czytałoby się jak usterka renderu. */
[data-when][data-anim="swap"] { transform: none; }

/* Wariant „wiersz znika z listy" — filtrowanie i szukanie.
   Różnica wobec `.anv-row[data-when]` (nowy rekord) jest zasadnicza: tam wiersz
   ma TRZYMAĆ miejsce w tabeli, żeby jego pojawienie się nie przesunęło reszty.
   Tutaj przeciwnie — panel przeładowuje stronę z filtrem w adresie i tabela
   naprawdę się skraca, więc wiersz musi wyjść z układu. Samo wygaszenie
   zostawiłoby w tabeli sześć pustych pasów i od razu byłoby widać atrapę. */
.anv-row[data-when][data-anim="rows"] { display: none; }
.anv-row[data-when][data-anim="rows"].is-on { display: flex; transform: none; opacity: 1; }

/* ── Przewijanie zawartości ekranu ──
   Widok listy zleceń jest wyższy niż kadr sceny — i taki jest naprawdę: sześć
   kafli, dwa wykresy po 220 px i dopiero potem tabela. Zamiast ściskać wykresy
   do wysokości, której nie mają, scena przewija zawartość tak, jak zrobiłby to
   użytkownik kółkiem myszy. Dystans podaje sama scena w `--anv-scroll`.

   Wyjęte z ogólnej reguły [data-when] niżej: warstwa ma być widoczna zawsze,
   a stan zmienia jej przesunięcie, nie krycie. */
.anv-scroll { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; transition: transform .85s cubic-bezier(.4, 0, .2, 1); }
.anv-scroll.is-on { transform: translateY(var(--anv-scroll, calc(var(--u) * -4))); }

/* ── Kursor ──────────────────────────────────────────────────────────────── */
.anv-cursor {
    position: absolute;
    z-index: 20;
    left: 0; top: 0;
    width: calc(var(--u) * 1.5);
    height: calc(var(--u) * 1.5);
    pointer-events: none;
    opacity: 0;
    transform: translate3d(var(--cx, 50%), var(--cy, 50%), 0);
    transition: transform var(--anv-move, 700ms) cubic-bezier(.3, .9, .25, 1), opacity .3s ease;
    will-change: transform;
}
.anv-cursor.is-on { opacity: 1; }
/* Kontrskala — przy zoomie scena rośnie, kursor ma zostać w swojej wielkości. */
.anv-cursor__g {
    display: block;
    width: 100%; height: 100%;
    transform: scale(var(--anv-inv, 1));
    transform-origin: 0 0;
    transition: transform .85s cubic-bezier(.4, 0, .2, 1);
}
.anv-cursor svg { display: block; width: 100%; height: 100%; filter: drop-shadow(0 2px 4px rgb(9 14 22 / 45%)); transition: transform .12s ease; }
.anv-cursor.is-press svg { transform: scale(.82); }
.anv-cursor__ring {
    position: absolute;
    left: 0; top: 0;
    width: calc(var(--u) * 2.2);
    height: calc(var(--u) * 2.2);
    margin: calc(var(--u) * -1.1) 0 0 calc(var(--u) * -1.1);
    border-radius: 50%;
    border: 2px solid rgb(71 145 178 / 85%);
    opacity: 0;
    transform: scale(.2);
}
.anv-cursor.is-press .anv-cursor__ring { animation: anv-ring .55s cubic-bezier(.2, .8, .2, 1); }
@keyframes anv-ring {
    0%   { opacity: .9; transform: scale(.2); }
    100% { opacity: 0;  transform: scale(1.9); }
}

/* ── Pasek kroków pod oknem: akcja → reakcja → rezultat ──────────────────── */
.anv-demo__steps {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: .75rem;
    max-width: 1120px;
    margin: 1.6rem auto 0;
}
@media (max-width: 640px) { .anv-demo__steps { grid-template-columns: 1fr; gap: .5rem; } }
.anv-demo__step {
    padding: .85rem 1rem .85rem 1.05rem;
    border: 1px solid rgb(var(--c-border, 227 232 238));
    border-left: 3px solid rgb(var(--c-border, 227 232 238));
    border-radius: 10px;
    background: #fff;
    transition: border-color .3s ease, background-color .3s ease, box-shadow .3s ease, transform .3s ease;
}
.anv-demo__step.is-on {
    border-left-color: rgb(var(--c-accent-500, 71 145 178));
    background: rgb(var(--c-accent-50, 238 245 249));
    box-shadow: 0 8px 22px -16px rgb(15 27 38 / 55%);
    transform: translateY(-2px);
}
.anv-demo__step b {
    display: block;
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: rgb(var(--c-ink-3, 107 114 128));
    margin-bottom: .2rem;
    transition: color .3s ease;
}
.anv-demo__step.is-on b { color: rgb(var(--c-accent-600, 47 122 153)); }
.anv-demo__step span { display: block; font-size: .875rem; color: rgb(var(--c-ink-2, 75 85 99)); line-height: 1.45; }

.anv-demo__note {
    max-width: 1120px;
    margin: .9rem auto 0;
    font-size: .75rem;
    color: rgb(var(--c-ink-3, 107 114 128));
    text-align: center;
}

/* ── Sterowanie odtwarzaniem ─────────────────────────────────────────────── */
/* Pauza jest wymagana przez WCAG 2.2.2 dla ruchu trwającego dłużej niż 5 s.
   Przycisk wychodzi z cienia na hover i na fokus klawiatury. */
.anv-demo__ctl {
    position: absolute;
    z-index: 8;
    right: .6rem;
    bottom: .6rem;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .35rem .7rem;
    border: 0;
    border-radius: 999px;
    background: rgb(16 25 34 / 78%);
    color: #fff;
    font-size: .72rem;
    font-weight: 600;
    cursor: pointer;
    opacity: 0;
    transition: opacity .25s ease, background-color .2s ease;
}
.anv-demo__window:hover .anv-demo__ctl,
.anv-demo__ctl:focus-visible { opacity: 1; }
.anv-demo__ctl:hover { background: rgb(16 25 34 / 92%); }

/* ── Mniej ruchu ─────────────────────────────────────────────────────────── */
/* Silnik przy prefers-reduced-motion ustawia scenę na klatce końcowej i nie
   uruchamia pętli (patrz js/panel-demo.js). Tu wygaszamy jeszcze przejścia
   i kursor, żeby nawet pierwsze wejście w widok nie mignęło ruchem. */
@media (prefers-reduced-motion: reduce) {
    .anv-stage, .anv-stage * { transition-duration: .01ms !important; animation: none !important; }
    .anv-cursor { display: none; }
    .anv-demo__live b { animation: none; }
    .anv-demo__tab-progress { display: none; }
}
