/*
 * Fleet dashboard styles (doc 08, 8.7).
 *
 * ── The density decision, stated once ───────────────────────────────────────
 *
 * 8.7 asks for a "sűrű, információ-központú felület — ez üzemeltetői eszköz,
 * nem marketing oldal". This stylesheet reads that as a rule about the DATA
 * layer, not about the whole page, and splits the two deliberately:
 *
 *   · the summary layer (sidebar, KPI tiles, fleet ribbon, card chrome) is
 *     generous — large radii, soft elevation, room to breathe;
 *   · the data layer (the site table, the timeline) stays tight, because the
 *     table is the product's reason to exist and "1000 sorból a 3 fontosat
 *     kell látni" (8.1) is impossible at marketing-page spacing.
 *
 * Polish went where there is room for it. If a future change starts inflating
 * table row padding, that is the line being crossed.
 *
 * ── Colour, and the two problems worth solving deliberately ─────────────────
 *
 * 1. Brand green vs "ok" green. The palette comes from the WP Fleet logo
 *    (#14563D / #0E4633). Both are dark greens, and the status colour for "ok"
 *    is also green (8.7). Brand greens are therefore used ONLY for chrome
 *    (sidebar, primary buttons, links) and never for a status, while the ok
 *    status uses a brighter, more saturated green that reads as a state rather
 *    than as furniture.
 *
 * 2. Status colour is never the only channel. The ok/critical pair sits at
 *    CVD ΔE 4.1 (deutan, OKLab ×100) — inherent to a red/green semantic, which
 *    8.7 mandates. Measured, not guessed. The mitigation is structural: every
 *    status carries an ICON and a TEXT LABEL beside the colour (see .badge and
 *    Html::badge()), so a red/green-blind operator reads state without hue.
 *
 * Two token families, because a mark and a text run need different contrast:
 *   --ok / --warning / --critical              marks: meter fills, edges, cells
 *   --ok-ink / --warning-ink / --critical-ink  text: badge labels, values
 * Every ink token in both themes is ≥ 4.5:1 on the surface behind it; the mark
 * tokens are ≥ 3:1 except light-mode warning (1.83:1), which is exactly why the
 * icon + label pairing above is mandatory rather than decorative.
 *
 * Dark mode is SELECTED, not an inverted light theme — its own steps, measured
 * against the dark surface. Declared under both the prefers-color-scheme media
 * query (OS setting) and [data-theme] (the toggle), so the toggle wins in both
 * directions. tests/dashboard_ui_test.php asserts the two dark blocks stay
 * identical; they are hand-duplicated because a media query cannot be reused
 * as a plain selector.
 */

/* ── Tokens: light ──────────────────────────────────────────────────────── */

:root {
    color-scheme: light;

    --brand:        #14563D;
    --brand-dark:   #0E4633;
    --brand-light:  #1E7052;
    --brand-ink:    #0F5C40;
    --brand-tint:   #E9F2EE;
    --brand-tint-2: #D8E8E0;

    --ok:           #0CA30C;
    --warning:      #FAB219;
    --critical:     #D03B3B;

    --ok-ink:       #0B6B33;
    --warning-ink:  #7A4E00;
    --critical-ink: #A32219;

    --ok-bg:        #E7F6E9;
    --warning-bg:   #FDF3DC;
    --critical-bg:  #FBEBEA;

    --ok-line:      #B6E3C0;
    --warning-line: #EFD9A4;
    --critical-line:#F2C9C4;

    --ink:          #101513;
    --ink-soft:     #566360;
    --ink-faint:    #67736E;
    --line:         #E6EBE8;
    --line-strong:  #D5DDD9;
    --surface:      #FFFFFF;
    --surface-2:    #F7FAF8;
    --surface-hover:#F1F6F3;
    --canvas:       #EFF3F1;

    --side:         #FFFFFF;
    --side-ink:     #566360;

    --shadow-1: 0 1px 2px rgba(14, 26, 20, .04), 0 6px 18px -8px rgba(14, 26, 20, .10);
    --shadow-2: 0 2px 4px rgba(14, 26, 20, .05), 0 14px 32px -10px rgba(14, 26, 20, .14);
    --shadow-3: 0 4px 8px rgba(14, 26, 20, .06), 0 26px 56px -12px rgba(14, 26, 20, .20);
    --ring:     0 0 0 3px rgba(30, 112, 82, .28);
    /* A --ring egy BOX-SHADOW érték. Kilenc szabály `outline: 2px solid
       var(--ring)`-ként használta, amiből `outline: 2px solid 0 0 0 3px
       rgba(...)` lett — érvénytelen, tehát a böngésző az egész deklarációt
       eldobta, és a márka-fókuszgyűrű helyett a gyári kék jelent meg. Az
       outline SZÍNT kér, ezért kapott sajátot. */
    --ring-color: #1E7052;

    --r-sm:  9px;
    --r:     14px;
    --r-lg:  18px;
    --r-pill: 999px;

    --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 20px; --s6: 28px;

    --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --mono: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace;

    /* Motion. Every duration is a token so prefers-reduced-motion can flatten
       the whole system in one place instead of rule by rule. */
    --dur-fast: 180ms;
    --dur:      380ms;
    --dur-slow: 900ms;
    --stagger:  34ms;
    --ease:     cubic-bezier(.22, .61, .36, 1);
    --ease-out: cubic-bezier(.16, 1, .3, 1);
    --lift:     -3px;
    --pulse:    1;
    /* AI-pill. 2026-09-04-ig arany volt, de az arany a borostyán warning-pirula
       MELLETT ült, és két sor távolságból ugyanannak a színnek látszott — pont
       az veszett el, amiért a jel létezik. Megmérve: deutan ΔE az arany
       kitöltés és a warning kitöltés között 12.6, a két tinta között 6.5 —
       vagyis vörös/zöld-vak szemmel gyakorlatilag egy szín. Ugyanez az ibolyával
       125.4. Az ibolya ráadásul sehol máshol nem szerepel a rendszerben.
       Kontraszt: a fehér felirat 7.1:1 a kitöltés alján és 5.6:1 a világosabb
       tetején; --ai-1 10.9:1 a világos felületen, 9.6:1 a sötéten.
       --ai-1 a lap hátterén álló tinta (pl. a flotta-fül ikonja), --ai-on a
       kitöltésen álló felirat — a kettő nem cserélhető. */
    --ai-1: #4C1D95;
    --ai-2: #5B21B6;
    --ai-3: #6D28D9;
    --ai-on: #FFFFFF;
    --ai-glow: rgba(109, 40, 217, .34);
    --ai-glint: rgba(255, 255, 255, .85);
}

/* ── Tokens: dark (OS setting) ──────────────────────────────────────────── */

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;

        --brand:        #1E7052;
        --brand-dark:   #14563D;
        --brand-light:  #2E9068;
        --brand-ink:    #5BC79A;
        --brand-tint:   #16241F;
        --brand-tint-2: #1D3329;

        --ok:           #0CA30C;
        --warning:      #FAB219;
        --critical:     #D03B3B;

        --ok-ink:       #4ADE80;
        --warning-ink:  #FBBF24;
        --critical-ink: #F87171;

        --ok-bg:        #12301F;
        --warning-bg:   #332612;
        --critical-bg:  #3A1C1A;

        --ok-line:      #1E5636;
        --warning-line: #5B451A;
        --critical-line:#5E2E2A;

        --ink:          #E9F0EC;
        --ink-soft:     #9FADA7;
        --ink-faint:    #828F8A;
        --line:         #232B28;
        --line-strong:  #313B37;
        --surface:      #141917;
        --surface-2:    #1A211E;
        --surface-hover:#202826;
        --canvas:       #0B0F0E;

        --side:         #141917;
        --side-ink:     #9FADA7;

        --shadow-1: 0 1px 2px rgba(0, 0, 0, .44);
        --shadow-2: 0 10px 28px -8px rgba(0, 0, 0, .62), 0 2px 4px rgba(0, 0, 0, .40);
        --shadow-3: 0 22px 52px -12px rgba(0, 0, 0, .72), 0 4px 10px rgba(0, 0, 0, .50);
        --ring:     0 0 0 3px rgba(91, 199, 154, .34);
        --ring-color: #5BC79A;
    /* Sötét témán a kitöltés marad telített, csak a lap hátterén álló tinta
       világosodik ki — fehér felirat ibolya alapon mindkét témában ugyanaz. */
    --ai-1: #C4B5FD;
    --ai-2: #A78BFA;
    --ai-3: #6D28D9;
    --ai-on: #FFFFFF;
    --ai-glow: rgba(167, 139, 250, .30);
    --ai-glint: rgba(255, 255, 255, .45);
    }
}

/* ── Tokens: dark (explicit toggle) ─────────────────────────────────────── */

:root[data-theme="dark"] {
    color-scheme: dark;

    --brand:        #1E7052;
    --brand-dark:   #14563D;
    --brand-light:  #2E9068;
    --brand-ink:    #5BC79A;
    --brand-tint:   #16241F;
    --brand-tint-2: #1D3329;

    --ok:           #0CA30C;
    --warning:      #FAB219;
    --critical:     #D03B3B;

    --ok-ink:       #4ADE80;
    --warning-ink:  #FBBF24;
    --critical-ink: #F87171;

    --ok-bg:        #12301F;
    --warning-bg:   #332612;
    --critical-bg:  #3A1C1A;

    --ok-line:      #1E5636;
    --warning-line: #5B451A;
    --critical-line:#5E2E2A;

    --ink:          #E9F0EC;
    --ink-soft:     #9FADA7;
    --ink-faint:    #828F8A;
    --line:         #232B28;
    --line-strong:  #313B37;
    --surface:      #141917;
    --surface-2:    #1A211E;
    --surface-hover:#202826;
    --canvas:       #0B0F0E;

    --side:         #141917;
    --side-ink:     #9FADA7;

    --shadow-1: 0 1px 2px rgba(0, 0, 0, .44);
    --shadow-2: 0 10px 28px -8px rgba(0, 0, 0, .62), 0 2px 4px rgba(0, 0, 0, .40);
    --shadow-3: 0 22px 52px -12px rgba(0, 0, 0, .72), 0 4px 10px rgba(0, 0, 0, .50);
    --ring:     0 0 0 3px rgba(91, 199, 154, .34);
    --ring-color: #5BC79A;
    /* Sötét témán a kitöltés marad telített, csak a lap hátterén álló tinta
       világosodik ki — fehér felirat ibolya alapon mindkét témában ugyanaz. */
    --ai-1: #C4B5FD;
    --ai-2: #A78BFA;
    --ai-3: #6D28D9;
    --ai-on: #FFFFFF;
    --ai-glow: rgba(167, 139, 250, .30);
    --ai-glint: rgba(255, 255, 255, .45);
}

/* ── Base ───────────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--canvas);
    color: var(--ink);
    font-family: var(--sans);
    font-size: 13.5px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

a { color: var(--brand-ink); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 { margin: 0; font-weight: 650; letter-spacing: -.02em; text-wrap: balance; }

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--brand-light);
    outline-offset: 2px;
    border-radius: var(--r-sm);
}

::selection { background: var(--brand-tint-2); color: var(--ink); }

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

.skip {
    position: absolute; left: -9999px; top: 0; z-index: 100;
    padding: 10px 14px;
    background: var(--surface); color: var(--ink);
    border-radius: var(--r-sm); box-shadow: var(--shadow-2);
}

.skip:focus { left: var(--s3); top: var(--s2); }

/* The sprite is a definition block, never a rendered element. */
.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Icons are solid fills that inherit the surrounding ink — see View\Icons for
   why this is currentColor and never a literal hex. */
/* ── Ikonok ─────────────────────────────────────────────────────────
   A FESTÉST a szimbólum hordozza (`fill="none" stroke="currentColor"` a
   vonalasokon, `fill="currentColor"` a négy tömör státusz-jelölésen), a
   VONALVASTAGSÁGOT viszont ez.

   Miért itt: egy 24-es rácsra rajzolt 2-es vonal 14 pixelre kicsinyítve
   1,17 pixel — vékonyabb, mint amit a szem ugyanabban a sorban a betűktől
   megszokott, és a 18 pixeles ikon mellett halványabbnak is látszik. A
   `stroke-width` öröklődő SVG-tulajdonság, tehát a `<use>` shadow tree-jébe
   beleöröklődik — de CSAK akkor, ha a szimbólum nem mondja meg magának.
   Ezért nincs rajta: onnan nem lehetne méretenként korrigálni. */
.ic { width: 18px; height: 18px; flex: 0 0 auto; display: block; stroke-width: 2.1; }
.ic--sm { width: 14px; height: 14px; stroke-width: 2.5; }
.ic--lg { width: 20px; height: 20px; stroke-width: 1.9; }

/* A jelvény-glyph tömör, nincs mit vastagítani rajta. */
.ic--badge { width: 11px; height: 11px; }

/* ── Shell: sidebar + content ───────────────────────────────────────────── */

.shell { display: flex; min-height: 100vh; align-items: flex-start; }

.side {
    position: sticky; top: 0;
    flex: 0 0 232px; height: 100vh;
    display: flex; flex-direction: column; gap: var(--s2);
    padding: var(--s5) var(--s3);
    background: var(--side);
    border-right: 1px solid var(--line);
}

/* The mark stands alone — it contains the wordmark, so nothing is set beside
   it. Near-square (948:895), so it is held to 60% of the rail and centred:
   at full width the block was tall enough to push the navigation down. */
.side__brand { padding: 0 var(--s2) var(--s4); }

.side__logo {
    display: block;
    width: 60%;
    height: auto;
    margin-inline: auto;
    color: var(--brand-ink);
}

.side__name { font-size: 15px; font-weight: 650; letter-spacing: -.02em; display: block; }
.side__name--sm { font-size: 12.5px; }
.side__sub { display: block; color: var(--ink-faint); font-size: 11px; font-weight: 500; }

.side__label {
    padding: var(--s3) var(--s2) 2px;
    color: var(--ink-faint);
    font-size: 10px; font-weight: 700;
    text-transform: uppercase; letter-spacing: .09em;
}

.side__nav { display: flex; flex-direction: column; gap: 2px; }

.side__nav a {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 10px;
    border-radius: var(--r);
    color: var(--side-ink);
    font-size: 13.5px; font-weight: 500;
    transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.side__nav a:hover { background: var(--surface-hover); color: var(--ink); text-decoration: none; }

.side__nav a.is-active {
    background: var(--brand); color: #fff; font-weight: 600;
    box-shadow: var(--shadow-1);
}


.side__foot { margin-top: auto; display: flex; flex-direction: column; gap: var(--s2); }

.side__user {
    display: flex; align-items: center; gap: var(--s2);
    padding: var(--s2);
    border-radius: var(--r);
    background: var(--surface-2);
}

.side__user form { margin: 0; margin-left: auto; }

.avatar {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; flex: 0 0 auto;
    border-radius: 50%;
    background: var(--brand-tint-2); color: var(--brand-ink);
    font-size: 12px; font-weight: 700;
}

.main { flex: 1 1 auto; min-width: 0; padding: var(--s5) var(--s6) 56px; }

/* ── Page head ──────────────────────────────────────────────────────────── */

.pagehead { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s5); }

.pagehead h1 { font-size: 22px; }
.pagehead .sub { color: var(--ink-faint); font-size: 13px; }

/*
 * A `flex-wrap` és a `min-width: 0` NEM kozmetika.
 *
 * A sor tartalma — kereső + „Új site" + téma-kapcsoló — 386 pixel, mert a
 * `<input type="search">` alapértelmezett `size=20` attribútuma miatt a
 * flexbox `min-width: auto`-ja húsz karakternyi minimumot ad neki. Tördelés
 * nélkül ez 375 és 320 pixel közötti telefonokon kilóg, és mivel a `.pagehead`
 * a LAYOUTBAN van, minden egyes oldalt oldalra görgethetővé tesz.
 *
 * A `min-width: 0` engedi a mezőnek, hogy a rendelkezésre álló helyre
 * zsugorodjon; a `flex-wrap` pedig azt, hogy ha így sem fér, akkor új sorba
 * kerüljön ahelyett, hogy szétfeszítené a lapot.
 */
.pagehead__tools {
    margin-left: auto;
    display: flex; align-items: center; gap: var(--s2);
    flex-wrap: wrap;
    min-width: 0;
}

.pagehead__tools form { margin: 0; display: flex; align-items: center; gap: var(--s2); flex: 1 1 16rem; min-width: 0; }

/* A gomb és a téma-kapcsoló SOSEM zsugorodik és nem is nő: a fenti `16rem`
   bázis a keresőmezőé, és amikor az „Új site" még az űrlapon belül ült, ez a
   bázis rá is vonatkozott — az Oldalak lapon, ahol nincs keresőmező, ettől a
   téma-kapcsoló a következő sorba került. A gomb azóta az űrlapon kívül van;
   ez a szabály tartja ott. */
.pagehead__tools > .btn { flex: 0 0 auto; }

.search { position: relative; display: flex; align-items: center; flex: 1 1 auto; min-width: 0; }

.search > .ic { position: absolute; left: 11px; color: var(--ink-faint); pointer-events: none; }

.search input {
    width: 250px;
    /* A `size=20` miatti belső minimum felülírása — enélkül a mező nem
       hajlandó a saját húsz karakterénél keskenyebb lenni. */
    min-width: 0;
    padding: 9px 12px 9px 34px;
    border: 1px solid var(--line); border-radius: var(--r-pill);
    background: var(--surface); color: var(--ink);
    font: inherit; font-size: 13px;
    transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease), width var(--dur) var(--ease-out);
}

.search input::placeholder { color: var(--ink-faint); }
.search input:focus { outline: none; border-color: var(--brand-light); box-shadow: var(--ring); width: 290px; }

.search__kbd {
    position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
    padding: 1px 6px;
    border: 1px solid var(--line); border-radius: 5px;
    color: var(--ink-faint); font-family: var(--mono); font-size: 11px;
    pointer-events: none; transition: opacity var(--dur-fast) var(--ease);
}

.search:focus-within .search__kbd { opacity: 0; }

/* ── Buttons ────────────────────────────────────────────────────────────── */

.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    padding: 8px 14px;
    border: 1px solid var(--line); border-radius: var(--r-pill);
    background: var(--surface); color: var(--ink-soft);
    font: inherit; font-size: 13px; font-weight: 550; line-height: 1.35;
    white-space: nowrap; cursor: pointer;
    transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
                color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.btn:hover { background: var(--surface-hover); color: var(--ink); border-color: var(--line-strong); text-decoration: none; }
.btn:active { transform: scale(.96); }

/* The primary action. The plain .btn above is the secondary style — what
   used to be called .btn--ghost, before the ghost became the default.
   Three templates still asked for `btn--ghost` long after the rule was
   deleted; they got `.btn` and looked right by accident. Removed. */
.btn--primary { background: var(--brand); border-color: var(--brand); color: #fff; box-shadow: var(--shadow-1); }
.btn--primary:hover { background: var(--brand-dark); border-color: var(--brand-dark); color: #fff; }

.btn--icon { padding: 8px; width: 36px; height: 36px; flex: 0 0 auto; }
/* Halkabb, kisebb másodlagos gomb — táblasorokban és panelek alján. */
.btn--sm { padding: 5px 11px; font-size: 12px; }
.btn--block { width: 100%; }

/*
 * Theme toggle. Both glyphs ship in the markup and CSS reveals the one that
 * matches the active theme, so the button is correct on first paint even
 * though the theme is only known at runtime — no JS needed to render it right.
 */
.theme-toggle .icon-moon { display: none; }
.theme-toggle .icon-sun { display: block; }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: block; }
    :root:not([data-theme="light"]) .theme-toggle .icon-sun { display: none; }
}

:root[data-theme="dark"] .theme-toggle .icon-moon { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: none; }

.btn[disabled], .btn.is-disabled {
    border-color: var(--line); background: var(--surface-2);
    color: var(--ink-faint); cursor: not-allowed;
}

.btn[disabled]:hover, .btn.is-disabled:hover { background: var(--surface-2); border-color: var(--line); transform: none; }

/* ── Entrance choreography ──────────────────────────────────────────────── */

.rise {
    animation: rise var(--dur-slow) var(--ease-out) backwards;
    animation-delay: calc(var(--i, 0) * var(--stagger));
}

@keyframes rise {
    from { opacity: 0; transform: translateY(14px) scale(.985); }
    to   { opacity: 1; transform: none; }
}

/* ── Cards ──────────────────────────────────────────────────────────────── */

.card {
    border: 1px solid var(--line); border-radius: var(--r-lg);
    background: var(--surface); box-shadow: var(--shadow-1);
    overflow: hidden;
    overflow: clip;   /* keeps sticky <th> working — clip is not a scrollport */
}

.card__head {
    display: flex; align-items: center; gap: var(--s2);
    padding: var(--s4) var(--s5);
    font-size: 14px; font-weight: 650; letter-spacing: -.01em;
}

.card__head > .ic { color: var(--ink-faint); }

/* ── Fleet ribbon (8.1: the whole fleet at a glance) ────────────────────── */

.ribbon-card { padding: var(--s5); }

.ribbon-head { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin-bottom: var(--s4); }
.ribbon-head h2 { font-size: 15px; }
.ribbon-head .sub { color: var(--ink-faint); font-size: 12.5px; }

.ribbon-legend { margin-left: auto; display: flex; gap: var(--s4); font-size: 12px; color: var(--ink-soft); flex-wrap: wrap; }
.ribbon-legend span { display: inline-flex; align-items: center; gap: 6px; }

.swatch { width: 9px; height: 9px; border-radius: 3px; display: inline-block; flex: 0 0 auto; }
.swatch--critical { background: var(--critical); }
.swatch--warning { background: var(--warning); }
/* Matches the resting opacity of an ok cell in the ribbon, so the legend
   swatch and the thing it labels are the same colour on screen. */
.swatch--ok { background: var(--ok); opacity: .4; }

.ribbon { display: grid; grid-template-columns: repeat(auto-fill, minmax(9px, 1fr)); gap: 3px; align-items: end; }

.ribbon a {
    height: 24px; border-radius: 3px;
    background: var(--ok); opacity: .3;
    transform-origin: bottom;
    animation: cell-in var(--dur-slow) var(--ease-out) backwards;
    animation-delay: calc(var(--i, 0) * 1.6ms);
    transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

@keyframes cell-in { from { transform: scaleY(.1); opacity: 0; } }

.ribbon a:hover { opacity: 1; transform: scaleY(1.3); }

.ribbon a.cell--warning { background: var(--warning); opacity: .85; }

.ribbon a.cell--critical {
    background: var(--critical); opacity: 1; height: 31px;
    animation: cell-in var(--dur-slow) var(--ease-out) backwards, breathe 2.6s var(--ease) infinite;
}

/* Critical cells breathe: slow and low-amplitude, so the eye is drawn without
   the page strobing. --pulse collapses it for reduced motion. */
@keyframes breathe {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--critical) calc(var(--pulse) * 50%), transparent); }
    50%      { box-shadow: 0 0 0 5px color-mix(in srgb, var(--critical) 0%, transparent); }
}

/* ── Metric tiles (KPI row) ─────────────────────────────────────────────── */

/*
 * `min(215px, 100%)` és nem `215px`.
 *
 * A csupasz minimum minden szélességen érvényes: egy 200 pixeles konténerben a
 * rács ragaszkodik a 215-höz, és kilóg. A `min()`-nel a sáv a szűk helyen a
 * konténerhez igazodik, tágasban meg viselkedik úgy, ahogy eddig — és nem kell
 * hozzá töréspont, ami egy jövőbeli elrendezésnél megint kimaradhat.
 */
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(215px, 100%), 1fr)); gap: var(--s3); margin-bottom: var(--s3); }

.metric {
    position: relative; overflow: clip;
    padding: var(--s5);
    border: 1px solid var(--line); border-radius: var(--r-lg);
    background: var(--surface); box-shadow: var(--shadow-1);
    transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}

.metric:hover { transform: translateY(var(--lift)); box-shadow: var(--shadow-2); }

/* Icon chip: rounded square, soft tint, solid icon. */
.chip {
    display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px;
    border-radius: 12px;
    background: var(--surface-2); color: var(--ink-soft);
    margin-bottom: var(--s3);
}

.metric--brand .chip { background: var(--brand-tint); color: var(--brand-ink); }
.metric--critical .chip { background: var(--critical-bg); color: var(--critical-ink); }
.metric--warning .chip { background: var(--warning-bg); color: var(--warning-ink); }

/* Proportional figures on purpose: tabular-nums gives every digit the width of
   a zero, which makes a number like 121 look loose at display size. Tabular is
   for COLUMNS (see .t-num). */
.metric__value { font-size: 34px; font-weight: 680; line-height: 1.05; letter-spacing: -.035em; }

.metric--critical .metric__value { color: var(--critical-ink); }
.metric--warning .metric__value { color: var(--warning-ink); }

.metric__label { margin-top: 3px; color: var(--ink-soft); font-size: 12.5px; font-weight: 500; }

/* A mértékegység a szám mellett, de nem a szám súlyával: a „/100" nem adat,
   csak a skála, és ugyanakkora fokozatban a 100-at olvashatatlanná tenné. */
.metric__unit { font-size: 15px; font-weight: 600; color: var(--ink-faint); letter-spacing: 0; }

/* A csempe másodlagos sora: amihez a nagy szám képest jelent valamit.
   Vonallal elválasztva, mert különben a felirat folytatásának látszik. */
.metric__sub {
    margin-top: var(--s3); padding-top: var(--s2);
    border-top: 1px solid var(--line);
    font-size: 12px; color: var(--ink-faint);
}

.metric__sub .kv { padding: 2px 0; }

/* Az EGYETLEN trend-jelölés a felületen, és nem szín: az irányt a nyíl-ikon
   hordozza, a jelentést a felirat. Szikragörbe nincs — a séma nem tárol napi
   pillanatképet, tehát az kitalált vonal lenne. */
.metric__delta { display: flex; align-items: center; gap: 5px; margin-top: 3px; }
.metric__delta > .ic { color: var(--ink-faint); }

.metric__link { display: inline-block; margin-top: var(--s2); font-size: 12px; font-weight: 550; }

/* ── Wave bar ───────────────────────────────────────────────────────────── */

.wave { position: relative; overflow: clip; padding: var(--s5); margin-bottom: var(--s3); }

.wave--halted { border-color: var(--critical-line); background: var(--critical-bg); }

.wave__head { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.wave__summary { font-weight: 650; font-size: 14px; letter-spacing: -.01em; }
.wave__actions { margin-left: auto; }

/* Meter: the fill carries severity, the track is a lighter step of the SAME
   hue, so the bar reads as one object in one state. */
.progress {
    position: relative; height: 9px; margin-top: var(--s3);
    border-radius: var(--r-pill); background: var(--brand-tint-2); overflow: clip;
}

.progress__bar {
    height: 100%; border-radius: var(--r-pill);
    background: linear-gradient(90deg, var(--brand), var(--brand-light));
    transition: width var(--dur-slow) var(--ease-out);
}

/* A sheen travelling the fill says the wave is working right now. */
.progress__bar::after {
    content: ""; position: absolute; inset: 0;
    background: linear-gradient(100deg, transparent 20%, color-mix(in srgb, #fff 42%, transparent) 50%, transparent 80%);
    transform: translateX(-100%);
    animation: sheen 2.4s var(--ease) infinite;
    opacity: var(--pulse);
}

@keyframes sheen { to { transform: translateX(100%); } }

/* The budget block: a label, a level, and a sentence saying what the number
   is. The sentence is not decoration — the figure is an estimate computed from
   a hand-typed price table, and presenting it as an invoice is what made a
   drained balance look like a system fault. */
.budget { margin-top: var(--s4); }
.budget__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; }
.budget__label { font-weight: 600; color: var(--ink-soft); }
.budget__note { margin-top: var(--s2); font-size: 12px; color: var(--ink-faint); }

/* A LEVEL, not an activity.
   The sheen on .progress means "this is moving right now" — true of a running
   wave, false of a month's spend against its budget. Same track, same fill
   geometry, no motion, and the fill carries severity once the level matters. */
.progress--level .progress__bar::after { content: none; }
.progress--level .progress__bar { background: linear-gradient(90deg, var(--ok), var(--ok)); }
.progress--level.is-warning { background: var(--warning-line); }
.progress--level.is-warning .progress__bar { background: var(--warning); }
.progress--level.is-critical { background: var(--critical-line); }
.progress--level.is-critical .progress__bar { background: var(--critical); }

.wave--halted .progress { background: var(--critical-line); }
.wave--halted .progress__bar { background: var(--critical); }
.wave--halted .progress__bar::after { animation: none; }

.wave__reason { margin-top: var(--s2); color: var(--critical-ink); font-size: 12.5px; }

/* ── Tables — the dense layer ───────────────────────────────────────────── */

.table-scroll { overflow-x: auto; }

table { width: 100%; border-collapse: collapse; }

th {
    position: sticky; top: 0; z-index: 1;
    padding: 9px var(--s5);
    border-bottom: 1px solid var(--line);
    background: var(--surface-2); color: var(--ink-faint);
    font-size: 10.5px; font-weight: 700; text-align: left;
    text-transform: uppercase; letter-spacing: .07em; white-space: nowrap;
}

/* A sticky header floating over rows needs its own bottom edge — the row
   border scrolls away underneath it. */
th::after { content: ""; position: absolute; inset: auto 0 -1px 0; height: 1px; background: var(--line); }

td { padding: 9px var(--s5); border-bottom: 1px solid var(--line); vertical-align: top; }

tbody tr {
    transition: background var(--dur-fast) var(--ease);
    animation: rise var(--dur) var(--ease-out) backwards;
    animation-delay: calc(140ms + var(--i, 0) * var(--stagger));
}

tbody tr:hover { background: var(--surface-hover); }
tbody tr:last-child td { border-bottom: none; }

.t-num { text-align: right; font-variant-numeric: tabular-nums; }
.t-mono { font-family: var(--mono); font-size: 12px; font-variant-numeric: tabular-nums; }
.t-soft { color: var(--ink-soft); }
.t-faint { color: var(--ink-faint); }
.t-nowrap { white-space: nowrap; }

/* Attention-first ordering is the point of the fleet table (8.1), so the rows
   that need attention carry a visible left edge — the eye finds them without
   reading the badge column. The badge still carries icon + label; this edge is
   a locator, never the only signal. */
tr.row--critical td:first-child, tr.row--warning td:first-child { position: relative; }

tr.row--critical td:first-child::before, tr.row--warning td:first-child::before {
    content: ""; position: absolute; left: 0; top: 4px; bottom: 4px; width: 3px;
    border-radius: 0 3px 3px 0;
    transition: width var(--dur) var(--ease-out);
}

tr.row--critical td:first-child::before { background: var(--critical); }
tr.row--warning td:first-child::before { background: var(--warning); }
tr.row--critical:hover td:first-child::before, tr.row--warning:hover td:first-child::before { width: 5px; }

tr.row--critical:hover { background: var(--critical-bg); }
tr.row--warning:hover { background: var(--warning-bg); }

/* Sortable columns (fleet.js). */
th.is-sortable { cursor: pointer; user-select: none; }
th.is-sortable:hover { color: var(--ink); background: var(--surface-hover); }

th.is-sortable::before {
    content: ""; display: inline-block; width: 0; height: 0;
    margin-right: 5px; vertical-align: 1px;
    border: 4px solid transparent; opacity: 0;
    transition: opacity var(--dur-fast) var(--ease);
}

th.is-sortable:hover::before { opacity: .35; border-bottom-color: currentColor; border-top-width: 0; }
th[aria-sort="ascending"]::before { opacity: 1; border-bottom-color: currentColor; border-top-width: 0; }
th[aria-sort="descending"]::before { opacity: 1; border-top-color: currentColor; border-bottom-width: 0; }
th[aria-sort]:not([aria-sort="none"]) { color: var(--ink); }

/* ── Badges ─────────────────────────────────────────────────────────────── */

.badge {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 2px 10px 2px 7px;
    border: 1px solid transparent; border-radius: var(--r-pill);
    font-size: 11.5px; font-weight: 650; line-height: 1.55; white-space: nowrap;
}

.badge--ok { background: var(--ok-bg); border-color: var(--ok-line); color: var(--ok-ink); }
.badge--warning { background: var(--warning-bg); border-color: var(--warning-line); color: var(--warning-ink); }
.badge--critical { background: var(--critical-bg); border-color: var(--critical-line); color: var(--critical-ink); }
.badge--neutral { background: var(--surface-2); border-color: var(--line); color: var(--ink-soft); }
.badge--brand { background: var(--brand-tint); border-color: var(--brand-tint-2); color: var(--brand-ink); }

.tag {
    display: inline-block; padding: 1px 8px;
    border-radius: var(--r-pill);
    background: var(--surface-2); color: var(--ink-soft);
    font-family: var(--mono); font-size: 11px; white-space: nowrap;
}

/* Panel-doboz: fejléc + törzs, kártyán belül.
   VISSZATETTEM. A site-oldal átépítésekor eltávolítottam, mert ott már nem
   használta senki — de a beállítások, a szerverek és a hullámok oldala igen,
   összesen 13 helyen, és azok kipadding nélkül maradtak. A grep a site.php-re
   szólt, a szabály az egész felületre. */
.side-box { margin-bottom: var(--s3); }
.side-box .card__head { font-size: 13px; padding-bottom: var(--s3); }
.side-box__body { padding: 0 var(--s5) var(--s5); }

/* ── Site detail ────────────────────────────────────────────────────────── */

.site-head {
    display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap;
    padding: var(--s5); margin-bottom: var(--s3);
    border: 1px solid var(--line); border-radius: var(--r-lg);
    background: var(--surface); box-shadow: var(--shadow-1);
}

.site-head__domain { font-size: 20px; font-weight: 680; letter-spacing: -.025em; }
.site-head__actions { margin-left: auto; display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
.site-head__actions form { margin: 0; display: inline-flex; }

/* ── A fejléc tény-chipjei ──────────────────────────────────────────
   Ugyanaz az adat, ami eddig egy `·`-elválasztású mondat volt. Ott a
   PHP-verzió megtalálásához végig kellett olvasni a sort; chipként
   mindegyik tény külön alakot kap, és a szem ráugrik arra, amelyiket
   keresi. A `.tag`-tól abban különbözik, hogy ez ikont is hordoz és nem
   monospace: ez nem gépi azonosító, hanem egy megnevezett tény. */
.site-head__chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--s3); }

.mchip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 3px 10px;
    border: 1px solid var(--line); border-radius: var(--r-pill);
    background: var(--surface); color: var(--ink-soft);
    font-size: 12px; white-space: nowrap; min-width: 0;
}

.mchip > .ic { color: var(--ink-faint); flex: none; }
.mchip__v { color: var(--ink); font-weight: 550; overflow-wrap: anywhere; }
a.mchip { color: var(--ink-soft); }
a.mchip:hover { background: var(--surface-hover); text-decoration: none; }

/* ── A „…" menü a fejlécben ─────────────────────────────────────────
   Öt egyenrangú gomb közül egyik sem hangsúlyos. Elöl marad három, a
   ritkán használtak ide kerülnek — nem tűnnek el, csak nem állítja róluk
   a fejléc, hogy egyformán fontosak.

   Natív `<details>`: a nyitás-csukás és a billentyűkezelés a böngészőtől
   jön, JS nélkül. */
.omenu { position: relative; }

.omenu__toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px;
    border: 1px solid var(--line); border-radius: var(--r);
    background: var(--surface); color: var(--ink-soft);
    cursor: pointer; list-style: none;
    transition: background var(--dur-fast) var(--ease);
}

/* A nyílra a Safari és a Chrome külön szabályt kér. */
.omenu__toggle::-webkit-details-marker { display: none; }
.omenu__toggle::marker { content: ''; }
.omenu__toggle:hover { background: var(--surface-hover); color: var(--ink); }
.omenu[open] .omenu__toggle { background: var(--surface-hover); color: var(--ink); }

.omenu__list {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
    display: flex; flex-direction: column; gap: var(--s2);
    min-width: 220px; padding: var(--s2);
    border: 1px solid var(--line); border-radius: var(--r);
    background: var(--surface); box-shadow: var(--shadow-3);
}

/* A menüben a gombok teljes szélességűek és balra igazítottak: itt egy
   LISTÁT olvas az ember, nem gombsort néz. */
.omenu__list .btn,
.omenu__list form,
.omenu__list form .btn { width: 100%; justify-content: flex-start; }

.columns { display: flex; gap: var(--s3); align-items: flex-start; }
.columns__main { flex: 1 1 auto; min-width: 0; }
.columns__side { flex: 0 0 300px; }

.filters { display: flex; gap: 5px; margin-left: auto; flex-wrap: wrap; }

.filters a {
    padding: 4px 12px;
    border: 1px solid var(--line); border-radius: var(--r-pill);
    background: var(--surface); color: var(--ink-soft);
    font-size: 12px; font-weight: 550;
    transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.filters a:hover { background: var(--surface-hover); color: var(--ink); text-decoration: none; }
.filters a.is-active { border-color: var(--brand); background: var(--brand); color: #fff; }

/* ── Timeline ───────────────────────────────────────────────────────────── */

.timeline { list-style: none; margin: 0; padding: 0; }

.timeline li {
    position: relative; display: flex; gap: var(--s3);
    padding: 11px var(--s5);
    border-top: 1px solid var(--line);
    animation: rise var(--dur) var(--ease-out) backwards;
    animation-delay: calc(var(--i, 0) * var(--stagger));
}

/* The icon column is a spine: a hairline runs the height of the list and each
   event is a node on it. This is what turns a list of rows into a chronology. */
.timeline li::before {
    content: ""; position: absolute; left: calc(var(--s5) + 13px);
    top: 0; bottom: 0; width: 2px; background: var(--line);
}

.timeline li:first-child::before { top: 18px; }
.timeline li:last-child::before { bottom: calc(100% - 32px); }

.timeline__icon {
    position: relative; z-index: 1; flex: 0 0 28px; height: 28px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 10px;
    border: 1px solid var(--line); background: var(--surface-2);
    color: var(--ink-soft);
    transition: transform var(--dur) var(--ease-out), border-color var(--dur) var(--ease),
                background var(--dur) var(--ease), color var(--dur) var(--ease);
}

/* Ugyanaz a korrekció, mint az `.ic--sm`-en: az idővonal a méretet osztály
   nélkül állítja, tehát a vonalvastagságot is neki kell. */
.timeline__icon .ic { width: 14px; height: 14px; stroke-width: 2.5; }

.timeline li:hover .timeline__icon {
    transform: scale(1.12);
    border-color: var(--brand-light); background: var(--brand-tint); color: var(--brand-ink);
}

.timeline__time {
    flex: 0 0 108px; padding-top: 5px;
    color: var(--ink-faint); font-family: var(--mono);
    font-size: 11.5px; font-variant-numeric: tabular-nums;
}

.timeline__body { flex: 1 1 auto; min-width: 0; padding-top: 3px; font-weight: 500; }

.timeline__meta {
    display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
    margin-top: 4px; color: var(--ink-faint); font-size: 11.5px; font-weight: 400;
}

.timeline li.sev--critical .timeline__icon { border-color: var(--critical-line); background: var(--critical-bg); color: var(--critical-ink); }
.timeline li.sev--warning .timeline__icon { border-color: var(--warning-line); background: var(--warning-bg); color: var(--warning-ink); }

/* ── Sidebar boxes ──────────────────────────────────────────────────────── */


.kv { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s2); padding: 4px 0; min-width: 0; }
.kv__k { color: var(--ink-soft); min-width: 0; overflow-wrap: anywhere; }

/*
 * A kulcshoz tartozó hosszú magyarázat, saját sorban.
 *
 * Amíg az érték mellett állt, a `.kv` flex-sora a kulcsot zsugorította, hogy a
 * szöveg elférjen — és mivel a kulcs `overflow-wrap: anywhere`, a szó KÖZEPÉN
 * tört el: az „OPcache"-ből „OPCac / he" lett. Külön sorban a kulcsnak megvan a
 * helye, a magyarázat pedig végre olvasható hosszban fut.
 */
.kv__note { color: var(--ink-faint); font-size: 12px; line-height: 1.45; padding: 0 0 4px; }

/* ── CVSS: a szám a kiemelt jel ──────────────────────────────────────────
   A szint eldönti, indul-e soron kívüli hullám; rendezni nem tud, mert
   tizenhét `critical` közül a címke nem mondja meg, melyik a 9.8. A szám
   igen — ezért kap saját, nagy, táblázatosan igazított helyet.

   A sáv színe MÁSODLAGOS: az információt maga a szám hordozza, és mellette
   ott a súlyosság-jelvény a saját alakjával, tehát a szín sehol nem áll
   egyedül (8.7). A `notice`/`info` sávnak nincs saját token-családja, és nem
   is kap: egy 5-ös nem „enyhén piros", hanem semleges. */
.cvss {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 46px; padding: 3px var(--s2);
    border: 1px solid transparent; border-radius: var(--r-sm);
    font-size: 16px; font-weight: 680; letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
}

.cvss--critical { background: var(--critical-bg); color: var(--critical-ink); border-color: var(--critical-line); }
.cvss--warning  { background: var(--warning-bg);  color: var(--warning-ink);  border-color: var(--warning-line); }
.cvss--notice   { background: var(--surface-2);   color: var(--ink-soft);     border-color: var(--line); }
.cvss--info     { background: var(--surface-2);   color: var(--ink-faint);    border-color: var(--line); }

/* Nincs pontszám: se nulla, se üres cella. A gondolatjel azt mondja, hogy
   megnéztük és a forrás nem közölt számot — alatta pedig az áll, amiért
   mégis sürgős lehet. */
.cvss--none { color: var(--ink-faint); font-size: 15px; font-weight: 600; }

.cvss__why { margin-top: 3px; font-size: 11.5px; line-height: 1.35; color: var(--ink-faint); }

.score { display: flex; align-items: center; gap: var(--s4); }
.score__value { font-size: 34px; font-weight: 680; letter-spacing: -.035em; line-height: 1; }
.score__grade { color: var(--ink-faint); font-weight: 650; font-size: 13px; }

.shot { display: block; width: 100%; border: 1px solid var(--line); border-radius: var(--r-sm); }

.empty { padding: 28px var(--s5); color: var(--ink-faint); text-align: center; }

/* ── Alerts ─────────────────────────────────────────────────────────────── */

.flash, .login__error {
    margin-bottom: var(--s3);
    padding: 11px var(--s4);
    border: 1px solid; border-radius: var(--r);
    font-size: 13px;
}

.flash { border-color: var(--ok-line); background: var(--ok-bg); color: var(--ok-ink); }
.login__error { border-color: var(--critical-line); background: var(--critical-bg); color: var(--critical-ink); }

/* ── Policy inheritance (8.5) ───────────────────────────────────────────── */

.inherit { color: var(--ink-faint); }
.inherit del { color: var(--ink-faint); text-decoration: line-through; }
.inherit strong { color: var(--brand-ink); }

/* ── Forms ──────────────────────────────────────────────────────────────── */

.field {
    padding: 8px 12px;
    border: 1px solid var(--line-strong); border-radius: var(--r-sm);
    background: var(--surface); color: var(--ink);
    font: inherit; font-size: 13.5px;
}

.field:focus { outline: none; border-color: var(--brand-light); box-shadow: var(--ring); }
.field::placeholder { color: var(--ink-faint); }

/*
 * A row of fields that belong together (host + user + port). It wraps rather
 * than crushes: three fields sharing a narrow column give each about 90px,
 * narrower than the placeholder text they exist to show. A capped field
 * (.u-w-port) still stays small — max-width clamps the flexed result.
 */
.form-row { display: flex; gap: var(--s2); flex-wrap: wrap; }
.form-row > .field { flex: 1 1 9em; }

/*
 * A labelled column of fields. `.form-stack` is the real name; `.login__form`
 * is an alias because the pattern outgrew the login page.
 */
.login__form, .form-stack { display: flex; flex-direction: column; gap: var(--s1); }

/* For a stack whose rows hold several fields side by side — .u-narrow (22em)
   is sized for one field per line. */
.form-stack--wide { max-width: 36em; }

.login__form label, .form-stack label {
    margin-top: 10px; color: var(--ink-soft);
    font-size: 12px; font-weight: 500;
}

.login__form input, .form-stack > input {
    padding: 9px 12px;
    border: 1px solid var(--line-strong); border-radius: var(--r-sm);
    background: var(--surface); color: var(--ink);
    font: inherit; font-size: 14px;
}

.login__form input:focus, .form-stack > input:focus {
    outline: none; border-color: var(--brand-light); box-shadow: var(--ring);
}

.login__form .btn, .form-stack .btn { margin-top: var(--s5); padding: 10px 14px; }

.form-stack .u-checkbox, .login__form .u-checkbox { margin-top: var(--s3); }
.form-stack .u-checkbox input, .login__form .u-checkbox input { width: auto; margin: 0; padding: 0; }

/*
 * Felvételi mód — SSH vagy csak connector.
 *
 * Rádió és nem fül, mert a választás az űrlap ADATA, nem nézet-állapot: JS
 * nélkül is átmegy a szerverre, és a szerver aszerint dönt, nem a mezők
 * kitöltöttségéből találgatva. A nem választott ág panelját a fleet.js rejti
 * el ÉS tiltja le; JS nélkül mindkét panel látszik, és a szerveroldali
 * ellenőrzés mondja meg, mi hiányzik.
 *
 * A specifikusság itt szándékos: a form-stack label szabálya (0,1,1) erősebb,
 * mint egy csupasz osztály, ezért a kártya-szabály két osztállyal indul.
 */
.route {
    display: flex; flex-direction: column; gap: var(--s2);
    margin: var(--s4) 0 0; padding: 0; border: 0;
}

.route__legend { padding: 0; color: var(--ink-soft); font-size: 12px; font-weight: 500; }

.route .route__option {
    display: flex; align-items: flex-start; gap: var(--s3);
    margin: 0; padding: var(--s3);
    border: 1px solid var(--line-strong); border-radius: var(--r);
    background: var(--surface); cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}

.route .route__option:hover { background: var(--surface-hover); }
.route .route__option:has(input:checked) { border-color: var(--brand); background: var(--brand-tint); }
.route .route__option:has(input:focus-visible) { box-shadow: var(--ring); }
.route .route__option input { width: auto; margin: 3px 0 0; padding: 0; accent-color: var(--brand); }

.route__body { display: flex; flex-direction: column; gap: var(--s1); }
.route__title { display: flex; align-items: center; gap: var(--s2); color: var(--ink); font-size: 13.5px; font-weight: 600; }
.route__desc { color: var(--ink-soft); font-size: 12px; line-height: 1.5; }

/* A választott ág mezői: a form-stack ritmusát folytatják, de behúzva és
   éllel megjelölve, hogy látszódjon, melyik választáshoz tartoznak. */
.route-panel {
    display: flex; flex-direction: column; gap: var(--s1);
    margin-top: var(--s2); padding-left: var(--s4);
    border-left: 2px solid var(--brand-tint-2);
}

.route-panel[hidden] { display: none; }

.route-panel > input {
    padding: 9px 12px;
    border: 1px solid var(--line-strong); border-radius: var(--r-sm);
    background: var(--surface); color: var(--ink);
    font: inherit; font-size: 14px;
}

.route-panel > input:focus { outline: none; border-color: var(--brand-light); box-shadow: var(--ring); }

/* ── Belépés ────────────────────────────────────────────────────────────── */

.login-page { background: var(--canvas); }

.login {
    max-width: 380px; margin: 12vh auto;
    padding: var(--s6) var(--s6) var(--s6);
    background: var(--surface);
    border: 1px solid var(--line); border-radius: var(--r-lg);
    box-shadow: var(--shadow-2);
}

.login__brand { display: flex; align-items: center; gap: 10px; margin-bottom: var(--s6); color: var(--brand-ink); }

/* The login card is a light surface in the light theme and a dark one in the
   dark theme; --brand-ink is the token that stays legible on both. */
.login__logo { height: 32px; width: auto; color: var(--brand-ink); }
.login__title { font-size: 16px; font-weight: 650; color: var(--brand-ink); letter-spacing: -.02em; }

/* ── Utilities ──────────────────────────────────────────────────────────── */

.u-mt   { margin-top: 6px; }
.u-mt-2 { margin-top: var(--s2); }
.u-mt-3 { margin-top: var(--s3); }
.u-mb-1 { margin-bottom: var(--s1); }
.u-mb-3 { margin-bottom: var(--s3); }
.u-m0   { margin: 0; }
.u-pre  { white-space: pre-wrap; }
.u-inline { display: inline; }
.u-normal { font-weight: 400; }
.u-w-meter { min-width: 170px; }
.u-narrow { max-width: 22em; }
.u-push-right { margin-left: auto; }
.u-hint { font-size: 12px; }
.u-w-port { max-width: 6em; }
.u-w-icon { width: 2em; }
.u-checkbox { display: flex; align-items: center; gap: var(--s2); margin-top: var(--s3); }

.u-code {
    margin: 0; padding: 0; background: none;
    white-space: pre-wrap; overflow-x: auto;
    font-family: var(--mono); font-size: 12px; color: var(--ink);
}

/* ── Cross-document view transitions ────────────────────────────────────── */

/*
 * The dashboard is server-rendered and multi-page, so navigation is a full
 * document load. This turns each one into a cross-fade instead of a flash of
 * white. It is progressive enhancement in the strictest sense: browsers
 * without support simply navigate as they always did, and there is no
 * JavaScript involved either way.
 */
@view-transition { navigation: auto; }

@media (prefers-reduced-motion: no-preference) {
    ::view-transition-old(root) { animation: vt-out 200ms var(--ease) both; }
    ::view-transition-new(root) { animation: vt-in 320ms var(--ease-out) both; }
}

@keyframes vt-out { to { opacity: 0; transform: translateY(-8px) scale(.99); } }
@keyframes vt-in { from { opacity: 0; transform: translateY(12px) scale(.99); } }

/* ── Responsive ─────────────────────────────────────────────────────────── */

@media (max-width: 1080px) {
    .columns { flex-direction: column; }
    .columns__side { flex: 1 1 auto; width: 100%; }
}

@media (max-width: 860px) {
    /*
     * `align-items` VISSZAÁLLÍTÁSA — enélkül a lap fele szélességben renderel.
     *
     * A `.shell` alapból `align-items: flex-start`, ami SOR-irányban azt jelenti,
     * hogy az oldalsáv és a tartalom felül igazodik — ez a szándék. Amint viszont
     * itt `column`-ra vált, ugyanaz a tulajdonság már a VÍZSZINTES méretezést
     * vezérli, és a gyerekek nem nyúlnak ki a teljes szélességre, hanem a saját
     * tartalmukhoz zsugorodnak.
     *
     * A tünet pontosan az volt, amit a hibajelentés mutatott: a navigációs sáv és
     * a kártyák a képernyő felén értek véget, a maradékot a háttér töltötte ki.
     * A `flex-start` sor-irányban helyes, oszlop-irányban hiba — a töréspontnak
     * mindkettőt át kell állítania, nem csak az irányt.
     */
    .shell { flex-direction: column; align-items: stretch; }

    .side {
        position: static; flex: 1 1 auto; width: 100%; height: auto;
        flex-direction: row; align-items: center; gap: var(--s3);
        overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--line);
        padding: var(--s2) var(--s3);
    }

    .side__label { display: none; }

    /* The rail is a horizontal bar here, so full width would push the whole
       navigation off screen — the mark goes back to a fixed height. */
    .side__brand { padding: 0 var(--s2) 0 0; flex: 0 0 auto; }
    .side__logo { width: auto; height: 30px; }

    .side__nav { flex-direction: row; }
    .side__nav a { white-space: nowrap; }
    .side__foot { margin-top: 0; margin-left: auto; }
    .side__user { background: none; padding: 0; }
    .side__user span:not(.avatar) { display: none; }

    .main { padding: var(--s4) var(--s3) 48px; }
    .pagehead__tools { width: 100%; margin-left: 0; }
    .search { flex: 1 1 auto; }
    .search input, .search input:focus { width: 100%; }
    .site-head__actions { margin-left: 0; width: 100%; }

    /* Sticky headers stop earning their keep once the sidebar wraps to a bar. */
    th { position: static; }
}

@media (prefers-reduced-motion: reduce) {
    :root {
        --dur-fast: 1ms; --dur: 1ms; --dur-slow: 1ms;
        --stagger: 0ms; --lift: 0px; --pulse: 0;
    }

    *, *::before, *::after {
        animation-duration: 1ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 1ms !important;
    }
}

@media print {
    .side, .site-head__actions, .filters, .btn, .pagehead__tools { display: none; }
    body { background: #fff; }
    .card, .metric, .wave { box-shadow: none; }
    .main { padding: 0; }
}

/* ── Chat ───────────────────────────────────────────────────────────────── */

.chat-thread {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
    max-height: 58vh;
    overflow-y: auto;
    padding-right: var(--s2);
}

.chat-turn { max-width: 90%; }
.chat-turn--user { align-self: flex-end; }

.chat-turn__who {
    display: flex;
    gap: var(--s2);
    align-items: baseline;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--ink-soft);
    margin-bottom: 3px;
}

.chat-turn__body {
    padding: 8px 11px;
    border: 1px solid var(--line);
    border-radius: var(--r);
    background: var(--surface-2);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.chat-turn--user .chat-turn__body {
    border-color: var(--brand-light);
    background: var(--brand-tint);
}

.chat-turn.is-pending .chat-turn__body { opacity: 0.6; font-style: italic; }

.chat-turn__tools { margin-top: 4px; font-size: 12px; }
.chat-turn__tools .tag { margin-right: 4px; }

/* ── Policy szerkesztő ───────────────────────────────────────────────────── */

/* ── Policy-űrlap ────────────────────────────────────────────────────
   Kétoszlopos: balra a címke és a magyarázat, jobbra a vezérlő.

   Korábban minden mező három sort foglalt — címke, vezérlő, súgó —
   teljes szélességben, egy 160 pixeles legördülővel. A sor 85%-a üres
   volt, a lap pedig nyolc képernyő hosszú. A magyarázat ugyanannyira
   fontos, mint a mező, de nem a mező ALATT: mellette olvasva egyszerre
   válaszol arra, hogy mi ez és mit állítson rajta az ember.

   A hajszálvonal teljes szélességben fut, mert a mezők a kártya
   párnázott törzsén KÍVÜL ülnek — ettől olvasódik egy űrlap sorainak,
   nem nyolc egymásra rakott doboznak. */
.policy-group__hint {
    margin: 0;
    padding: 0 var(--s5) var(--s4);
    color: var(--ink-soft);
    font-size: 13px;
}

.policy-field {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 20em);
    column-gap: var(--s5);
    align-items: start;
    padding: var(--s4) var(--s5);
    border-top: 1px solid var(--line);
}

.policy-field__head  { grid-column: 1; grid-row: 1; margin: 0; }
.policy-field__hint  { grid-column: 1; grid-row: 2; font-size: 12px; line-height: 1.5; margin-top: 4px; }

/* A vezérlő végigfut a címke ÉS a súgó magasságán, hogy a két oszlop
   teteje egy vonalban induljon. */
.policy-field__control { grid-column: 2; grid-row: 1 / span 2; }

/* Olyan mező, aminek nincs fogyasztója. Nem elrejtve: elrejteni azt
   jelentené, hogy elveszik egy beállítás, amit valaki már megválasztott.
   Kimondva viszont nem hisz benne senki tévesen. */
.policy-field__inert {
    grid-column: 1 / -1;
    display: flex;
    align-items: baseline;
    gap: var(--s2);
    margin-top: var(--s3);
    padding: var(--s3) var(--s4);
    border-radius: var(--r-sm);
    background: var(--warning-bg);
    color: var(--warning-ink);
    font-size: 12px;
    line-height: 1.5;
}

/* Ismétlődő sorok — a fagyasztási időszakok három mezője — a teljes
   szélességet kérik. A vezérlő-oszlopba szorítva félbetört dátumválasztók
   rongyos oszlopává esett szét. */
.policy-field__control--wide {
    grid-column: 1 / -1;
    grid-row: auto;
    margin-top: var(--s3);
}

@media (max-width: 760px) {
    .policy-field { grid-template-columns: minmax(0, 1fr); }
    .policy-field__control { grid-column: 1; grid-row: auto; margin-top: var(--s3); }
}

/* An inherited field is shown, but muted: the value is real and worth seeing,
   it just is not set here. */
.policy-field.is-inherited .policy-field__control { opacity: 0.55; }

.policy-field__head {
    display: flex;
    align-items: baseline;
    gap: var(--s2);
}

.policy-field__label,
.policy-field__toggle { font-weight: 600; font-size: 13px; }

.policy-field__toggle {
    display: flex;
    align-items: center;
    gap: var(--s2);
    cursor: pointer;
}

.policy-field__head .t-faint { margin-left: auto; font-size: 12px; font-weight: 400; }

.policy-field__control select.field,
.policy-field__control input.field { width: 100%; }
.policy-field__control .u-w-num { max-width: 8em; }
.policy-field__control--wide .form-row { align-items: center; }

.u-w-num { max-width: 8em; }

.repeat { width: 100%; }
.repeat .form-row { align-items: center; }


/* ── Hiányzó segéd-osztályok ────────────────────────────────────────
   A servers.php ezekre hivatkozott, a stíluslap sosem definiálta őket —
   ezért esett szét az az aloldal. */
.card__body { padding: var(--s4) var(--s5) var(--s5); }
.card--warning { border-color: var(--warning-line); }
.card--warning .card__head { color: var(--warning-ink); }

.list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s2); }
.list > li { display: flex; gap: var(--s2); align-items: baseline; font-size: 13px; }

.u-muted { color: var(--ink-faint); }

/* ── Teendők ────────────────────────────────────────────────────────
   A munkalista, ami szándékosan nem az idővonal: elemei állapotot
   váltanak és kipipálhatók, míg az idővonal csak hozzáfűzhető.
   A vizuális nyelv a .timeline-ét követi, hogy ne egy idegen komponens
   üljön a lap tetején. */
.tabs {
    display: flex;
    gap: var(--s1);
    padding: 0 var(--s5);
    border-bottom: 1px solid var(--line);
}

.tab {
    appearance: none;
    background: none;
    border: 0;
    border-bottom: 2px solid transparent;
    color: var(--ink-faint);
    font: inherit;
    font-size: 13px;
    font-weight: 550;
    padding: var(--s2) var(--s2) calc(var(--s2) - 2px);
    margin-bottom: -1px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    transition: color var(--dur-fast) var(--ease);
}
.tab:hover { color: var(--ink); }
.tab.is-active { color: var(--ink); border-bottom-color: var(--brand); }
.tab:focus-visible { outline: 2px solid var(--ring-color); outline-offset: -2px; border-radius: var(--r); }

.tab__count {
    font-variant-numeric: tabular-nums;
    background: var(--surface-hover);
    color: var(--ink-soft);
    border-radius: 999px;
    padding: 1px 7px;
    font-size: 11px;
    font-weight: 600;
}
.tab.is-active .tab__count { background: var(--brand-tint); color: var(--brand-ink); }

.tab-panel { display: none; }
.tab-panel.is-active { display: block; }

.attention { list-style: none; margin: 0; padding: 0; }

.attention__item {
    display: flex;
    align-items: flex-start;
    gap: var(--s4);
    padding: var(--s3) var(--s5);
    border-bottom: 1px solid var(--line);
    border-left: 3px solid transparent;
}
.attention__item:last-child { border-bottom: none; }
.attention__item--critical { border-left-color: var(--critical); background: var(--critical-bg); }
.attention__item--warning  { border-left-color: var(--warning); }

.attention__main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.attention__title { font-size: 13px; font-weight: 550; line-height: 1.45; }

.attention__meta {
    font-size: 12px;
    display: flex;
    flex-wrap: wrap;
    gap: var(--s1) 6px;
    align-items: center;
}

.attention__rec { font-size: 12px; color: var(--ink-soft); line-height: 1.5; }

.attention__done { flex: 0 0 auto; padding-top: 1px; }

/*
 * IKONGOMB, nem gombváltozat.
 *
 * Fix 28×28 négyzet egyetlen glyphnek — ez a teendők ✓ gombja. Szöveggel
 * megtöltve a felirat kilóg a dobozból, `.btn`-nel kombinálva pedig a két
 * szabály padding és sugár fölött veszekszik: a képernyőn egy karika mögül
 * lógott ki a „Teljes leltár" felirat.
 *
 * Szöveges, halk gombhoz `.btn .btn--sm` való.
 */
.btn--quiet {
    background: var(--surface);
    border: 1px solid var(--line);
    color: var(--ink-faint);
    border-radius: var(--r);
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    font-size: 13px;
    line-height: 1;
    transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.btn--quiet:hover { color: var(--ok-ink); border-color: var(--ok-line); background: var(--ok-bg); }
.btn--quiet:focus-visible { outline: 2px solid var(--ring-color); outline-offset: 2px; }

.attention + .empty, .tab-panel > .empty { padding: var(--s5); text-align: center; }

/* A settings.php ezekre hivatkozott, definiálva nem voltak. */
.table-wrap { overflow-x: auto; }
.table-wrap table, table.table { width: 100%; border-collapse: collapse; }
.u-mb-2 { margin-bottom: var(--s4); }


/* Egymást követő kártyák térköze.
   Eddig kártyánként, kézzel volt beállítva — egy új kártya ezért ráült a
   következőre, és ez minden későbbi kártyánál megismétlődött volna.

   A `.tabbed` is idetartozik: a site-oldalon a „Teendők" kártya UTÁN
   közvetlenül a fülsáv jön, és mivel a felsorolásból kimaradt, a két doboz
   térköz nélkül ült egymáson. Ez ugyanaz a hiba, amit a fenti komment leír —
   csak nem kártya volt a következő elem. */
.card + .card,
.metrics + .card,
.card + .columns,
.card + .tabbed,
.metrics + .tabbed,
/* A capture-sáv kártyája és az alatta álló oszlopok közé is kell a hézag —
   enélkül a BIZTONSÁG csoportcím a képek aljához tapadt. */
.card + .groups,
.card + .metrics { margin-top: var(--s3); }

/* ── AI-pill ────────────────────────────────────────────────────────
   Saját komponens, nem badge-változat. A badge-paletta SZEMANTIKUS
   (critical / warning / ok), és az „ezt a Fleet AI döntötte" nem
   súlyosság — bármelyik meglévő szín valótlant állítana a bejegyzésről.
   Az ibolya szándékosan kívül esik a palettán: a lapon semmi más nem
   használja, így a szem azonnal megtalálja az AI-sorokat.

   És ez az EGYETLEN kitöltött pirula a felületen. A többi — badge, chip,
   tab__count — halvány tintás alapon ül; ez teli színnel és halvány
   fénnyel áll, tehát még azelőtt kiválik, hogy a színét elolvasnád.
   Az arany azért bukott meg, mert ugyanez a kiválás nem történt meg:
   a warning-pirula borostyánjától két sor távolságból nem különbözött. */
.ai-pill {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 1px 8px 1px 5px;
    border: 1px solid var(--ai-2);
    border-radius: 999px;
    /* Enyhe függőleges átmenet, hogy plasztikus legyen, ne lapos folt. A két
       végpont ugyanaz a token, csak a felső van fehérrel keverve — így egy
       témaváltás nem tud kettéhasadni. A világosabb felső sávon is 4.6:1 a
       fehér felirat kontrasztja, tehát a fény nem eszi meg a szöveget. */
    background:
        linear-gradient(180deg, color-mix(in srgb, var(--ai-3) 88%, #fff) 0%, var(--ai-3) 100%);
    /* A kitöltésen álló felirat, nem a lap hátterén álló tinta. A kettő
       felcserélése az a hiba, amitől a logó egyszer fehéren állt a fehér
       login-kártyán — láthatatlanul. */
    color: var(--ai-on);
    box-shadow: 0 1px 2px var(--ai-glow), 0 0 0 3px color-mix(in srgb, var(--ai-glow) 45%, transparent);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    line-height: 1.55;
    vertical-align: baseline;
    /* A csillanás a pillen belül marad. */
    overflow: hidden;
    isolation: isolate;
}

/* A jelölés ikonja 12 pixel, kitöltött ibolya pirulán ül. Ott egy 2-es vonal
   fél pixelre kicsinyedik és eltűnik a kitöltésben — ezért a legvastagabb
   korrekció a lapon. A `fill`-t nem állítja: azt a szimbólum mondja meg. */
.ai-pill__ic {
    width: 12px;
    height: 12px;
    stroke-width: 2.7;
    flex: 0 0 auto;
}

.ai-pill__label { position: relative; z-index: 1; }

/* Az áthaladó fény. Ferde, keskeny sáv, ami balról jobbra fut át — és
   hosszú szünetet tart, mert egy folyamatosan villogó elem egy oldalnyi
   sorban zaklató, nem látványos. */
.ai-pill::after {
    content: "";
    position: absolute;
    inset: 0 auto 0 -60%;
    width: 45%;
    background: linear-gradient(100deg,
        transparent 0%,
        var(--ai-glint) 50%,
        transparent 100%);
    transform: skewX(-18deg);
    animation: ai-glint 5.5s var(--ease, ease) infinite;
    pointer-events: none;
}

/*
 * Szétcsúsztatva.
 *
 * Egy idővonalon tíz ilyen pill is állhat egymás alatt, és azonos animációval
 * MIND EGYSZERRE villan — ami pontosan az a zaklató villogás, amit a fenti
 * komment elkerülni akar. A `nth-of-type` néhány fix eltolást ad, tehát a
 * csillanások szétszóródnak anélkül, hogy egyetlen sor JS kellene hozzá.
 */
.ai-pill:nth-of-type(2n)::after   { animation-delay: 1.3s; }
.ai-pill:nth-of-type(3n)::after   { animation-delay: 2.7s; }
.ai-pill:nth-of-type(5n)::after   { animation-delay: 4.1s; }
.ai-pill:nth-of-type(7n)::after   { animation-delay: .7s; }

/* A robot ugyanabból az anyagból, mint a felirat, egy hajszállal halványabban
   — így a jel az ikon, nem a szöveg. A flotta-fülön viszont nincs kitöltés
   alatta, ott a lap hátterére való tinta jön (lásd .tab--fleet lentebb). */
.ai-pill__ic { color: var(--ai-on); opacity: .92; }

@keyframes ai-glint {
    0%        { left: -60%; }
    /* A mozgás az első 18%-ban lezajlik, a maradék 82% szünet. */
    18%, 100% { left: 130%; }
}

/* Egy demó nem ér annyit, hogy vestibuláris panasza legyen tőle valakinek. */
@media (prefers-reduced-motion: reduce) {
    .ai-pill::after { animation: none; opacity: 0; }
}

/* ── Esemény-részletek ──────────────────────────────────────────────
   Az üzenet egy mondat; a tételes rész — útvonalak, URL-ek, kódrészletek —
   kisebb betűvel alatta, és a harmadik sortól lenyitható. Korábban minden
   egyetlen mezőbe préselődött, és a szkript-riasztás sha256 hasheket
   sorolt fel egy bekezdésben. */
.detail { margin-top: 4px; display: flex; flex-direction: column; gap: 2px; }

.detail__line {
    font-family: var(--mono);
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--ink-soft);
    word-break: break-word;
    overflow-wrap: anywhere;
}

.detail__more { margin-top: 2px; }

.detail__more > summary {
    font-size: 11.5px;
    color: var(--ink-faint);
    cursor: pointer;
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    user-select: none;
}
.detail__more > summary::-webkit-details-marker { display: none; }
.detail__more > summary::before { content: "▸"; font-size: 9px; transition: transform var(--dur-fast) var(--ease); }
.detail__more[open] > summary::before { transform: rotate(90deg); }
.detail__more > summary:hover { color: var(--ink); }
.detail__more > summary:focus-visible { outline: 2px solid var(--ring-color); outline-offset: 2px; border-radius: var(--r); }

.detail__more[open] { display: flex; flex-direction: column; gap: 2px; }

/* ── Optimalizálás-doboz ────────────────────────────────────────────
   Karbantartási tételek az oldal saját lapján. Vizuálisan halkabb, mint a
   teendők: mindig van belőle, és soha nem sürgős — ha ugyanúgy nézne ki,
   ugyanúgy elszívná a figyelmet, mint amikor még a teendők közt volt. */
.card--quiet { border-color: var(--line); background: var(--surface); opacity: .92; }
.card--quiet .card__head { color: var(--ink-soft); font-weight: 600; }

.attention--quiet .attention__item {
    border-left-color: transparent;
    padding-top: var(--s2);
    padding-bottom: var(--s2);
}
.attention--quiet .attention__title { font-weight: 500; color: var(--ink-soft); }

/* ── Megjegyzések az AI-nak ─────────────────────────────────────────
   A lezáró jegyzet a gomb MELLETT van, nem külön lépésben: külön lépésként
   senki nem írná meg, pedig épp lezáráskor tudja az operátor a legpontosabban,
   mi volt az adott találat. */
.attention__close { flex: 0 0 auto; display: flex; gap: var(--s2); align-items: center; }

.attention__note {
    width: 15rem;
    max-width: 34vw;
    font: inherit;
    font-size: 12px;
    padding: 4px 8px;
    border: 1px solid var(--line);
    border-radius: var(--r);
    background: var(--surface);
    color: var(--ink);
}
.attention__note::placeholder { color: var(--ink-faint); }
.attention__note:focus-visible { outline: 2px solid var(--ring-color); outline-offset: -1px; }

/* A javasolt lépés gombként, a pipa mellett.
   Külön sorban, nem a pipa mellé zsúfolva: a kettő ellentétes döntés — az egyik
   azt mondja „rendben van így", a másik élő forgalmat kezd blokkolni —, és két
   egymás melletti kis gomb közül a rossz eltalálása itt drága tévedés. */
.attention__actions {
    flex: 0 0 auto;
    display: flex;
    gap: var(--s2);
    align-items: center;
    margin-top: var(--s2);
}
.attention__actions a.btn { text-decoration: none; }

.note-form { display: flex; gap: var(--s2); align-items: flex-start; margin-top: var(--s3); }
.note-form__input {
    flex: 1 1 auto;
    font: inherit;
    font-size: 13px;
    padding: 6px 9px;
    border: 1px solid var(--line);
    border-radius: var(--r);
    background: var(--surface);
    color: var(--ink);
    resize: vertical;
}
.note-form__input:focus-visible { outline: 2px solid var(--ring-color); outline-offset: -1px; }

.btn--link {
    background: none;
    border: 0;
    color: var(--ink-faint);
    font: inherit;
    font-size: 11px;
    text-decoration: underline;
    cursor: pointer;
    padding: 0 0 0 4px;
}
.btn--link:hover { color: var(--critical); }

@media (max-width: 46rem) {
    .attention__close { flex-direction: column; align-items: stretch; }
    .attention__note { width: 100%; max-width: none; }
}

.note-form__hint { font-size: 12px; line-height: 1.6; }
.u-inline { display: inline; }

/* Számoszlop: jobbra igazítva, azonos szélességű számjegyekkel — így a
   nagyságrendek egymás alatt összehasonlíthatók. */
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ── A rendben lévő oldalak hajtása ──────────────────────────────────
   A site-lista már csak azt sorolja fel, amin nyitott probléma van. A
   többi ide kerül, egy nyitható sáv mögé.

   Miért hajtás és nem elhagyás: az operátornak néha attól kell
   megnyugodnia, hogy egy oldal ott VAN és rendben van. A "nincs a
   listán" ettől megkülönböztethetetlen lenne attól, hogy kiesett a
   felügyeletből — és pont ez a rendszer legrosszabb hibamódja. */
.fold {
    border-top: 1px solid var(--line);
}

.fold > summary {
    padding: 12px 16px;
    font-size: 12.5px;
    color: var(--ink-faint);
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 6px;
    user-select: none;
}

.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::before { content: "▸"; font-size: 10px; transition: transform var(--dur-fast) var(--ease); }
.fold[open] > summary::before { transform: rotate(90deg); }
.fold > summary:hover { color: var(--ink); background: var(--surface-hover); }
.fold > summary:focus-visible { outline: 2px solid var(--ring-color); outline-offset: -2px; }

/* ── Lista-szűrő (az Oldalak lap) ────────────────────────────────────
   A mező a táblán KÍVÜL ül, de a kártyán BELÜL: a szűrő ahhoz a listához
   tartozik, ami alatta van, nem a laphoz. A fejléc keresője ezen az egy
   lapon ezért nincs kirenderelve — két egyforma mező egymás fölött azt
   kérdezné az operátortól, melyik melyikre hat. */
.listfilter {
    display: flex; align-items: center; gap: var(--s2);
    padding: 0 var(--s5) var(--s4);
    /* Három vezérlő: szöveg, ügyfél, gép. Keskeny ablakon inkább törjön sorba,
       mint hogy a legördülők olvashatatlanra zsugorodjanak — egy „38-silv…"
       feliratú tétel közül nem lehet választani. */
    flex-wrap: wrap;
}

.listfilter .search { flex: 1 1 16rem; }

/* A rekeszek nem nőnek a szabad helyre: a szöveges mező az, ami a maradékot
   megkapja. A `max-width` a hosszú ügyfélneveket fogja meg — a legördülő
   szélessége a leghosszabb tételéhez igazodna, és egyetlen hosszú név
   kiszorítaná a sorból a másik kettőt. */
.listfilter__pick { display: flex; flex: 0 1 auto; min-width: 0; }
.listfilter__pick select { max-width: 16rem; min-width: 0; font-size: 13px; padding: 8px 10px; }

/* Teljes szélesség, és fókuszban sem nő: a fejlécben azért tágul a mező,
   mert ott szűk helyen ül, itt viszont ez a lap fő eszköze és már eleve
   annyi helyet kap, amennyi van. */
.search--wide input,
.search--wide input:focus { width: 100%; }

/* A „Szűrés" gomb a JS nélküli útvonalé. Ha a fleet.js élesítette az
   azonnali szűrést (`data-live`), a gomb jelenléte azt sugallná, hogy meg
   KELL nyomni — pedig a lista már a gépelés közben szűrve van. */
.listfilter[data-live] .listfilter__submit { display: none; }

/* A találatszám a teljes darabszám MELLETT áll, nem helyette: a „7 találat"
   a „32 site" mellett azt is elmondja, mekkora halmazból szűrtünk. */
.tag--hit {
    background: var(--brand-tint); color: var(--brand-ink);
    border: 1px solid var(--brand-tint-2);
    font-family: inherit; font-size: 11.5px; padding: 0 7px;
}

/* A kiszűrt sor. A böngésző `[hidden]`-je önmagában is elrejtené, de a
   táblázat-sorok `display: table-row`-ja ugyanazon a szinten versenyez vele,
   és egy erősebb táblázat-szabály elég ahhoz, hogy a sor némán visszajöjjön —
   a szűrő pedig ettől egyszerűen nem csinálna semmit. */
tr[hidden] { display: none; }

/* ── Kapcsolat: csatornánként egy chip (az Oldalak lap) ─────────────
   A felirat mondja meg, HOGYAN kapcsolódik az oldal (SSH, Connector), a
   glyph azt, hogy az a csatorna most válaszol-e. Az egészséges csatorna
   szándékosan CSENDES: keretes chip kis ✓-vel, nem zöld pirula. 31 site
   két-két zöld pirulája zöldre festené a táblát, és pont az az egy piros
   veszne el benne, amiért az oszlop létezik — ugyanaz a figyelem-elv, amit
   a sorok rendje is követ. A bajban lévő csatorna a badge-ek színét kapja,
   mert azt KELL észrevenni. */
.chans { display: inline-flex; gap: 4px; vertical-align: middle; }

.chan {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 1px 8px 1px 6px;
    border: 1px solid var(--line-strong); border-radius: var(--r-pill);
    color: var(--ink-soft);
    font-size: 11.5px; font-weight: 600; line-height: 1.55; white-space: nowrap;
}

/* A ✓ a TINTA-tokent kapja, nem a jelölésit: a világos --ok fehér alapon
   3,4:1, ami 11 pixeles alakon kevés; a --ok-ink 6,6:1. A másik három
   állapot glyphje a chip szövegszínét örökli (`currentColor`). */
.chan--ok > .ic { color: var(--ok-ink); }

.chan--warning  { background: var(--warning-bg);  border-color: var(--warning-line);  color: var(--warning-ink); }
.chan--critical { background: var(--critical-bg); border-color: var(--critical-line); color: var(--critical-ink); }
.chan--neutral  { background: var(--surface-2);   border-color: var(--line);          color: var(--ink-faint); }

/* Az utolsó esemény: EGY sor — mikor, és milyen fajta —, az üzenet a
   tooltipben. Az üzenet korábban teljes hosszában a cellában állt, és egy
   400 karakteres AI-összefoglaló tíz sorosra nyújtotta a sorát. */
.evt { display: inline-flex; align-items: center; gap: 5px; color: var(--ink-soft); }
.evt > .ic { color: var(--ink-faint); }

/* ── Tooltip ─────────────────────────────────────────────────────────
   EGY doboz a <body> végén; a fleet.js tölti ki és teszi a `data-tip` elem
   alá (vagy fölé, ha lent nincs hely). Azért nem a cellában ül, mert a
   táblát a `.table-scroll` `overflow-x: auto`-ja vágja: egy cellán belül
   abszolút pozicionált buborék az alsó soroknál levágódna, vagy görgetősávot
   nyitna a táblán. `position: fixed`, a nézetablakhoz mérve, így semmilyen
   görgető doboz nem vágja meg.
   Fordított színű (tinta alapon a felület színe): átmeneti réteg, és így
   mindkét témában azonnal elválik a lap tartalmától — a szövegkontraszt
   ugyanaz a pár, mint a lap fő szövegéé, csak felcserélve. */
[data-tip] { cursor: help; }
[data-tip]:focus-visible { outline: 2px solid var(--ring-color); outline-offset: 2px; border-radius: var(--r-pill); }

.tipbox {
    position: fixed; left: 0; top: 0; z-index: 60;
    max-width: min(26rem, calc(100vw - 16px));
    padding: 8px 11px;
    border-radius: var(--r-sm);
    background: var(--ink); color: var(--surface);
    box-shadow: var(--shadow-3);
    font-size: 12px; line-height: 1.45;
    /* A szöveg soronként egy tény (a sablon `\n`-nel tagolja); a hosszú,
       szóköz nélküli részek — IP, útvonal, hash — törhetnek bárhol. */
    white-space: pre-line; overflow-wrap: anywhere;
    /* Nem fogja el az egeret: különben a buborék a mutató alá csúszva
       „elhagyná" a kiváltó elemet, és villogva nyílna-zárulna. */
    pointer-events: none;
    animation: tip-in var(--dur-fast) var(--ease-out);
}

.tipbox[hidden] { display: none; }
.tipbox__head { display: block; margin-bottom: 2px; font-weight: 700; }

@keyframes tip-in { from { opacity: 0; transform: translateY(3px); } }

/* A szalag darabszáma link az Oldalak lapra: a szalag megmutatja, HOGY
   valami piros, de nem mondja meg, melyik oldal az. */
.ribbon-head__link { text-decoration: none; border-bottom: 1px solid transparent; }
.ribbon-head__link:hover { color: var(--ink); border-bottom-color: var(--line); }

/* ── Teendő-bizonyíték ───────────────────────────────────────────────
   A cím megmondja MI a baj, az ajánlás azt, hogy mi a teendő. Ez a blokk
   a bizonyíték: a nyers hibaüzenet, a fájl, a sor, a kérés URL-je.

   Miért `pre` és miért nem tördeljük szabadon: egy PHP stack trace
   sorai jelentést hordoznak, és az egymásba folyatott szöveg pont azt
   az egy dolgot rontja el, amiért az operátor idenéz. Vízszintesen
   viszont a doboz görget, nem az oldal. */
.attention__detail { margin-top: 6px; }

.attention__detail > summary {
    font-size: 11.5px;
    color: var(--ink-faint);
    cursor: pointer;
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    user-select: none;
}

.attention__detail > summary::-webkit-details-marker { display: none; }
.attention__detail > summary::before { content: "▸"; font-size: 9px; transition: transform var(--dur-fast) var(--ease); }
.attention__detail[open] > summary::before { transform: rotate(90deg); }
.attention__detail > summary:hover { color: var(--ink); }
.attention__detail > summary:focus-visible { outline: 2px solid var(--ring-color); outline-offset: 2px; border-radius: var(--r); }

.attention__evidence {
    margin: 6px 0 0;
    padding: 10px 12px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--r);
    font-family: var(--mono);
    font-size: 11.5px;
    line-height: 1.55;
    color: var(--ink-soft);
    white-space: pre;
    overflow-x: auto;
    max-height: 22em;
    overflow-y: auto;
}

/* ── Site-oldal: fejléc-cím és oldal-szintű fülek ─────────────────────
   A státusz-pirula a domain MELLETT áll, nem alatta: együtt olvasva
   egy mondat („devbox.hu — kritikus"), külön sorban két adat. */
.site-head__title {
    display: flex;
    align-items: center;
    gap: var(--s3);
    flex-wrap: wrap;
}

.site-head__meta { margin-top: var(--s2); }

/* A kártyákon belüli fülsáv a kártya keretében ül. Az oldal-szintű
   sávnak nincs kerete, amiben ülhetne, ezért a saját felületét hozza —
   különben a semmiben lebegne a panelek fölött. */
.tabs--page {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border-bottom: 1px solid var(--line);
    padding: var(--s2) var(--s4) 0;
    box-shadow: var(--shadow-1);
}

.tabs--page .tab { font-size: 13.5px; padding: var(--s3) var(--s3) calc(var(--s3) - 2px); }

/* A panelek a sáv alatt folytatódnak, ezért a tetejük nem kerekedik. */
.tabbed > .tab-panel { margin-bottom: var(--s3); }
.tabbed > .tab-panel > .card:first-child { border-top-left-radius: 0; border-top-right-radius: 0; }

/* ── Áttekintés: három tematikus oszlop ─────────────────────────────
   Tíz egyenrangú szakasz állt egy `auto-fit` rácsban, egy kártyán belül,
   hajszálvonalakkal elválasztva. Két baja volt, és csak az egyik látszott.

   A LÁTHATÓ: a rács sorai egyforma magasak. A PHP-doboz tizenöt sora
   mellett az ötsoros Uptime a saját magasságának háromszorosát foglalta
   üresen, 1440 pixelen pedig négy hasáb lett belőle, tehát az utolsó
   sorban két üres cella maradt — hézag, aminek semmi jelentése nem volt.

   A MÉLYEBB: tíz doboz rangsor és csoport nélkül. Egy szabad masonry az
   elsőt megoldotta volna, a másodikat nem: ott a doboz oda esik, ahol
   éppen hely van. Oszloponként viszont minden doboz annyi helyet kap,
   amennyit elfoglal — ugyanaz a nyereség —, azzal a különbséggel, hogy
   itt az olvasási sorrend szándékos, és az oszlop NEVE megmondja, milyen
   kérdésre válaszolnak együtt.

   Ezért lettek külön kártyák a szakaszokból. A régi indoklás — „öt lebegő
   doboz úgy néz ki, mintha öt külön dologról szólna" — igaz volt; a
   csoportcím az, ami most megoldja helyette. */
.groups {
    display: grid;
    /* A `minmax(0, 1fr)` a `1fr` helyett: a `1fr` minimuma `auto`, és egy
       hosszú monospace IP vagy egy tördelhetetlen útvonal ettől szélesebbre
       nyomta volna az oszlopot, mint a harmada. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s3);
    align-items: start;
}

/* Két, majd egy hasáb. A csoportok sorrendje mindkettőben megmarad:
   biztonság → környezet → üzemeltetés. */
@media (max-width: 1240px) { .groups { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 820px)  { .groups { grid-template-columns: minmax(0, 1fr); } }

.group { display: flex; flex-direction: column; gap: var(--s3); min-width: 0; }

/* A csoport neve halkabb, mint bármelyik kártyafejléc alatta: nem tartalom,
   hanem a kérdés, amire az alatta álló dobozok együtt válaszolnak. */
.group__title {
    margin: 0 0 calc(var(--s1) * -1) 2px;
    font-size: 11.5px; font-weight: 700; letter-spacing: .08em;
    text-transform: uppercase; color: var(--ink-faint);
}

/* Egy doboz a csoporton belül: ugyanaz a kártya-kroma, mint a `.card`, de
   sűrűbb fejléccel — a fejléc itt szakaszcím, nem oldalcím. */
.ovbox {
    display: flex; flex-direction: column; min-width: 0;
    border: 1px solid var(--line); border-radius: var(--r-lg);
    background: var(--surface); box-shadow: var(--shadow-1);
    overflow: clip;
    transition: box-shadow var(--dur) var(--ease-out);
}

/* Árnyék, nem emelés. A `.metric` csempe megemelkedik hoverre, mert négy van
   belőle egy sorban; itt nyolc doboz áll három oszlopban, és nyolc ugráló
   kártya nyugtalan felület. */
.ovbox:hover { box-shadow: var(--shadow-2); }

.ovbox--quiet { border-color: var(--line); background: var(--surface); opacity: .92; }
.ovbox--quiet .card__head { color: var(--ink-soft); font-weight: 600; }

.ovbox > .card__head {
    font-size: 11.5px;
    font-weight: 620;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--ink-faint);
    padding: var(--s4) var(--s5) var(--s3);
}

.ovbox__body {
    padding: 0 var(--s5) var(--s5);
    flex: 1 1 auto;
}

/*
 * Dobozon belüli alcím.
 *
 * A hibakeresés a PHP alatt ugyanannak a kérdésnek a folytatása, nem külön
 * kártya — ezért halkabb, mint egy `card__head`, de vonallal elválik. A
 * Hírnév-doboz ezzel tartja külön az OLDAL és a GÉP hírnevét: egy kérdés,
 * két hatókör.
 */
.ovbox__sub {
    display: flex; align-items: center; gap: var(--s2);
    margin-top: var(--s4); padding-top: var(--s3);
    border-top: 1px solid var(--line);
    font-size: 11.5px; font-weight: 620; letter-spacing: .06em;
    text-transform: uppercase; color: var(--ink-faint);
}

/* Az ELSŐ alcím nem választ el semmit — fölötte a kártyafejléc áll, ami már
   megtette. Vonallal és felső hézaggal egy üres sáv nyílt a fejléc alatt. */
.ovbox__body > .ovbox__sub:first-child {
    margin-top: 0; padding-top: 0; border-top: 0;
}

/* ── Capture: rögzített magasság + nagyító ───────────────────────────
   Teljes oldalról készült kép, tehát tetszőlegesen magas. Felülről
   vágva mutatja azt, ami egy pillantásra elárulja, hogy él-e az oldal;
   a többi a nagyítóban olvasható. */
.shot-open {
    appearance: none;
    display: block;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    cursor: zoom-in;
    position: relative;
    border-radius: var(--r-sm);
}

.shot-open .shot {
    height: 220px;
    object-fit: cover;
    object-position: top center;
}

.shot-open__hint {
    position: absolute;
    right: var(--s2);
    bottom: var(--s2);
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 8px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line);
    opacity: 0;
    transition: opacity var(--dur-fast) var(--ease);
}

.shot-open:hover .shot-open__hint,
.shot-open:focus-visible .shot-open__hint { opacity: 1; }
.shot-open:focus-visible { outline: 2px solid var(--ring-color); outline-offset: 2px; }

/* A screenshot-nagyító: balra az előzmények, jobbra a kép.
   A hasáb fix szélességű és NEM nyúlik a kép rovására — egy dátum-idő pár
   szélessége adott, a képnek viszont minden pixel számít. */
.lightbox {
    width: min(96vw, 1400px);
    max-width: 96vw;
    height: 92vh;
    max-height: 92vh;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
    overflow: hidden;
}

/* A hasábok CSAK nyitott állapotban. A `display` a `[open]`-en van, nem a
   `.lightbox`-on: egy <dialog>-ot a böngésző saját stíluslapja rejti el, amíg
   nincs nyitva, a szerzői szabály viszont MINDIG erősebb annál — egy csupasz
   `.lightbox { display: flex }` ezért a csukott dialógust is kifestette, a
   dokumentum végén, backdrop nélkül, üres listával és forrás nélküli képpel. */
.lightbox[open] { display: flex; }

.lightbox::backdrop { background: rgba(8, 12, 10, .72); }

.lightbox__side {
    display: flex;
    flex: 0 0 244px;
    flex-direction: column;
    min-width: 0;
    border-right: 1px solid var(--line);
    background: var(--surface-2);
}

.lightbox__head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--s3) var(--s4);
    border-bottom: 1px solid var(--line);
}

.lightbox__path { overflow-wrap: anywhere; font-size: 13px; }
.lightbox__count { font-size: 12px; }

/* A lista görgeti magát, a kép külön — így a dátumok a helyükön maradnak,
   miközben egy teljes oldalas képen lefelé haladsz. */
.lightbox__list {
    flex: 1;
    margin: 0;
    padding: var(--s2) 0 var(--s4);
    overflow-y: auto;
    list-style: none;
}

.lightbox__times { margin: 0; padding: 0; list-style: none; }

/* A nap ragad: pár száz capture görgetése közben a dátum az egyetlen fogódzó,
   és pont az tűnne el elsőnek a hasáb tetején. */
.lightbox__day {
    position: sticky;
    top: 0;
    z-index: 1;
    margin: 0;
    padding: var(--s2) var(--s4);
    background: var(--surface-2);
    color: var(--ink-faint);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
}

.lightbox__entry {
    display: flex;
    flex-direction: column;
    gap: 1px;
    width: 100%;
    padding: var(--s2) var(--s4);
    border: 0;
    border-left: 3px solid transparent;
    background: none;
    color: var(--ink);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.lightbox__entry:hover { background: var(--surface-hover); }
.lightbox__entry:focus-visible { outline: 2px solid var(--ring-color); outline-offset: -2px; }

.lightbox__entry.is-current {
    border-left-color: var(--brand);
    background: var(--brand-tint);
    color: var(--brand-ink);
}

.lightbox__time { font-family: var(--mono); font-size: 13px; }
.lightbox__meta { color: var(--ink-faint); font-size: 11px; }

/* A nem-200 sor a felkiáltójelet a SZÖVEGÉBEN hozza (fleet.js), a szín csak
   megerősíti — 8.7: állapot soha nem csak szín. */
.lightbox__meta--warn { color: var(--warning-ink); }

.lightbox__note {
    padding: var(--s3) var(--s4);
    color: var(--ink-faint);
    font-size: 12px;
}

.lightbox__view {
    position: relative;
    flex: 1;
    min-width: 0;
    overflow: auto;
}

.lightbox img { display: block; width: 100%; height: auto; }

/* A szerzői `display: block` erősebb a böngésző [hidden] szabályánál, tehát a
   kép elrejtése JS-ből enélkül némán nem történne meg — a „már nem érhető el"
   üzenet a törött kép-ikon MELLETT jelenne meg, nem helyette. */
.lightbox [hidden] { display: none; }

.lightbox__gone {
    margin: 0;
    padding: var(--s6) var(--s4);
    color: var(--ink-faint);
    text-align: center;
}

/* A módválasztó és a bezárás EGY ragadós sávban. A bezáró korábban magában
   lebegett a kép jobb felső sarkában; három gomb ugyanígy lebegtetve a kép
   tetejét takarná ki, márpedig egy teljes oldalas capture-nél pont a fejléc az,
   amit először néz meg az ember. */
.lightbox__bar {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s2) var(--s2) var(--s2) var(--s4);
    background: var(--surface);
    border-bottom: 1px solid var(--line);
}

.lightbox__against { font-size: 12px; margin-left: auto; }

/* A lap tetején a `.tabs` egy szakaszhatár, itt egy kapcsoló egy sávon belül:
   ugyanaz a gomb, alsó vonal és oldalpadding nélkül. */
.tabs--inline { padding: 0; border-bottom: 0; }

.lightbox__close {
    flex: 0 0 auto;
    appearance: none;
    width: 32px;
    height: 32px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--surface);
    color: var(--ink);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
}

.lightbox__close:hover { background: var(--surface-hover); }
.lightbox__close:focus-visible { outline: 2px solid var(--ring-color); outline-offset: 2px; }

/* ── Csúszkás összehasonlítás ─────────────────────────────────────
   Az ALSÓ réteg a mostani állapot, és az adja a doboz magasságát: az a kérdés
   tárgya, és egy oldal, ami időközben magasabb lett, ne nyújtsa meg a dobozt a
   régi állapot kedvéért. A felső réteg a baseline, balról levágva — a csúszkától
   balra a RÉGI, jobbra a MOSTANI, ami az egyetlen sorrend, amit nem kell
   megmagyarázni. */
.shotcmp { position: relative; overflow: hidden; line-height: 0; }

.shotcmp__now { display: block; width: 100%; height: auto; }

.shotcmp__was {
    position: absolute;
    inset: 0;
    overflow: hidden;
    /* A vágást `clip-path` végzi, nem szélesség: a levágott réteg így NEM
       nyomódik össze, hanem takarásba kerül — szélességgel vágva a baseline
       oldala minden csúszkamozdulatra összenyomódna, és minden pixel rossz
       helyen állna. A JS ezt az egy változót írja. */
    clip-path: inset(0 50% 0 0);
}

.shotcmp__was img { display: block; width: 100%; height: auto; }

/* A választóvonal. A `left`-et a JS állítja, együtt a vágással. */
.shotcmp__line {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 2px;
    margin-left: -1px;
    background: var(--brand);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .55);
    pointer-events: none;
}

.shotcmp__tag {
    position: absolute;
    top: var(--s3);
    z-index: 1;
    padding: 3px 8px;
    border-radius: var(--r);
    background: rgba(8, 12, 10, .7);
    color: #fff;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.4;
    pointer-events: none;
}

.shotcmp__tag--left  { left: var(--s3); }
.shotcmp__tag--right { right: var(--s3); }

/* A range a teljes képet lefedi, láthatatlanul: a fogantyú bárhol megfogható,
   a billentyűzetes kezelés és a képernyőolvasó-kimondás pedig ingyen jár. */
.shotcmp__range {
    position: absolute;
    inset: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
    margin: 0;
    appearance: none;
    background: transparent;
    cursor: ew-resize;
}

.shotcmp__range::-webkit-slider-thumb {
    appearance: none;
    width: 28px;
    height: 28px;
    border: 2px solid var(--brand);
    border-radius: 999px;
    background: var(--surface);
    cursor: ew-resize;
}

.shotcmp__range::-moz-range-thumb {
    width: 28px;
    height: 28px;
    border: 2px solid var(--brand);
    border-radius: 999px;
    background: var(--surface);
    cursor: ew-resize;
}

.shotcmp__range:focus-visible { outline: 2px solid var(--ring-color); outline-offset: -2px; }

.lightbox__diff { display: block; width: 100%; height: auto; }

/* Keskeny kijelzőn a hasáb a kép FÖLÉ kerül, nem mellé: 244 pixel oldalt egy
   telefonon a képből venné el a helyet, amiért a nagyítót megnyitották. */
@media (max-width: 720px) {
    .lightbox { flex-direction: column; }

    .lightbox__side {
        flex: 0 0 auto;
        max-height: 34vh;
        border-right: 0;
        border-bottom: 1px solid var(--line);
    }
}

/* ── Külső tiltólisták ───────────────────────────────────────────────
   A közös `.list` egy sorba teszi a gyerekeit. Itt három van — pirula,
   mondat, gyártók — és egy keskeny hasábban ettől a mondat egyetlen szó
   széles oszlopba tört, a gyártónév pedig külön lebegett mellette.

   Soronként egy forrás: a neve pirulaként a saját sorában, a megállapítás
   alatta teljes szélességben. */
.rep { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s3); }

.rep__item { display: block; font-size: 13px; }

.rep__msg {
    margin-top: 5px;
    color: var(--ink-soft);
    line-height: 1.5;
}

.rep__vendors {
    margin-top: 3px;
    font-size: 12px;
    color: var(--ink-faint);
}

/* A gép címe monospace-ben: egy IP-t karakterenként olvas az ember,
   amikor egy tiltólista-találatot ellenőriz. */
.rep__ip {
    font-family: var(--mono);
    font-size: 13px;
    color: var(--ink);
    margin-bottom: var(--s2);
}

/* ── Lemaradó site-ok a feed-sorban ──────────────────────────────────
   A sor korábban annyit mondott, hogy „2 site lemaradt". Ez a lista
   megnevezi őket, és megkülönbözteti a két esetet: a régi verzió még
   fut valamit, a „soha nem szinkronizált" semmit. */
.feed-lag { list-style: none; margin: var(--s2) 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.feed-lag > li { display: flex; align-items: center; gap: var(--s2); font-size: 12.5px; }
.feed-lag__next { margin-top: var(--s2); font-size: 12px; line-height: 1.5; max-width: 46em; }

/* ── Veszélyes műveletek doboza ──────────────────────────────────────
   Vizuálisan elkülönítve és a lap ALJÁN. Egy űrlap, amiben a „mentés"
   és a „mindent töröl" egymás mellett ül, olyan űrlap, amit előbb-utóbb
   rosszul használ valaki. */
.card--danger { border-color: var(--critical-line); }
.card--danger > .card__head { color: var(--critical-ink); }

.btn--danger {
    background: var(--critical-bg);
    border-color: var(--critical-line);
    color: var(--critical-ink);
    font-weight: 600;
}

.btn--danger:hover { background: var(--critical-line); color: var(--critical-ink); border-color: var(--critical-ink); }

/* A Fleet-jelölés a fülsávban: ugyanaz az ibolya, mint a pillen, csak kitöltés
   és keret nélkül — egy fülön a kitöltött pirula dobozt csinálna a dobozban.
   Ezért NEM a --ai-on jön ide (az a kitöltésen álló fehér, ami a világos
   fülsávban láthatatlan lenne), hanem a lap hátterére való tinta. */
.tab--fleet .ai-pill__ic { color: var(--ai-2); margin-right: 2px; }
.tab--fleet.is-active .ai-pill__ic { color: var(--ai-1); }

/* ── Telefon ─────────────────────────────────────────────────────────
   A 860px-es töréspont az oldalsávot vízszintes sávvá alakítja, ami
   tabletig elég. Ez alatt más a probléma: nem az elrendezés, hanem a
   VÍZSZINTES TÚLCSORDULÁS. Egy dashboardon a leggyakoribb hiba, hogy
   egyetlen széles elem — egy ötfüles sáv, egy tábla, egy hosszú domain
   — oldalirányba tolja az EGÉSZ lapot, és onnantól minden sor csúszik.

   A szabály itt: ami széles, az a saját dobozában görögjön, a body soha. */
@media (max-width: 560px) {
    /* A fülsáv görgethető, nem tördelt. Tördelve két-három sorba állna,
       és a lap tetejének fele navigáció lenne. */
    .tabs {
        overflow-x: auto;
        flex-wrap: nowrap;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
        padding-inline: var(--s3);
    }

    .tabs::-webkit-scrollbar { display: none; }
    .tab { flex: 0 0 auto; }

    /* Egy hosszú domain nem tolhatja szét a fejlécet. */
    .site-head { padding: var(--s4); }
    .site-head__domain { font-size: 17px; overflow-wrap: anywhere; }
    .site-head__title { gap: var(--s2); }

    /* A gombsor a fejléc alatt, teljes szélességben — négy gomb egy
       telefonon egymás mellett használhatatlanul keskeny. */
    .site-head__actions { gap: var(--s2); }
    .site-head__actions .btn { flex: 1 1 auto; justify-content: center; }

    /* A „…" menü telefonon nem lebeg, hanem kinyílik a helyén.
       Abszolút pozícióban a 220 pixeles lista a saját gombjához igazodik, és
       ha a gombsor tördelt, az a gomb bárhol lehet — egy 360 pixeles kijelzőn
       ez oldalra lógó lapot jelentett. Beágyazva nem tud kilógni. */
    .omenu { width: 100%; }
    .omenu__toggle { width: 100%; }
    .omenu__list {
        position: static; min-width: 0; margin-top: var(--s2);
        box-shadow: none;
    }

    /* A `.groups` a 820px-es töréspontnál már egy hasáb; itt a csempesor
       csuk össze. */
    .metrics { grid-template-columns: minmax(0, 1fr); }

    /* Kártya-párnázás egy fokkal szűkebbre: telefonon a tartalomnak kell
       a hely, nem a keretnek. */
    .card__head, .ovbox > .card__head { padding-inline: var(--s4); }
    .ovbox__body, .side-box__body, .policy-group__hint { padding-inline: var(--s4); }
    .policy-field { padding-inline: var(--s4); }

    /*
     * ── A felső menü telefonon ─────────────────────────────────────
     *
     * Hét menüpont felirattal ~600 pixel. Egy 390 pixeles telefonon ez
     * vízszintesen görgő sávvá válik, amiből a felhasználó hármat lát, és
     * semmi nem árulja el, hogy van még — ez volt a hibajelentés.
     *
     * Ikon-alapúra váltunk: hét ikon kifér egyszerre, tehát nincs görgetés
     * és nincs rejtett menüpont. A felirat nem `display: none`-nal tűnik el
     * — az a képernyőolvasótól is elvenné —, hanem vizuálisan rejtve marad,
     * és a link `title`-je is hordozza.
     */
    .side { overflow-x: visible; gap: var(--s2); padding: var(--s2); }

    /* A márkajel elfér, de nem viheti el a hét ikon helyét. */
    .side__logo { height: 24px; }
    .side__brand { padding-right: 0; flex: 0 0 auto; }

    .side__nav { flex: 1 1 auto; justify-content: space-evenly; gap: 0; }

    /*
     * RUGALMAS, nem rögzített szélesség.
     *
     * Rögzített 40 pixellel a hét ikon + a logó + a kilépés 373 pixel, ami
     * 390-en kifér és egy 360-as vagy 320-as telefonon kilóg — és a kilógás
     * megint vízszintes görgetés lenne, vagyis pont az a hiba, amit ez a
     * blokk megszüntetni hivatott. Így viszont a sáv a rendelkezésre álló
     * helyre OSZTJA szét a hét gombot, és nem lóghat ki soha.
     *
     * A felső korlát azért kell, hogy szélesebb telefonon se hízzanak
     * aránytalanná; az alsó azért, hogy ujjal még eltalálhatók maradjanak.
     */
    .side__nav a {
        flex: 1 1 0;
        min-width: 34px;
        max-width: 46px;
        height: 40px;
        justify-content: center;
        padding: 0;
        gap: 0;
    }

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

    /* Ikonként az aktív állapotot a kitöltés hordozza, nem a felirat. */
    .side__nav a.is-active .ic { transform: scale(1.08); }

    /* A kilépés a sáv jobb szélén marad, avatar nélkül — a nevet úgyis
       tudja, aki bejelentkezett. */
    .side__foot { flex: 0 0 auto; }
    .side__user .avatar { display: none; }

    /* A „/" billentyű-tipp telefonon értelmetlen — nincs billentyűzet, amivel
       fókuszba lehetne ugrani —, és a keresőmezőből visz el helyet. */
    .search__kbd { display: none; }
    .search input { padding-right: 12px; }

    /* Hivatkozások a teendő-listában és a feed-sorokban: hosszú domain nem
       feszítheti szét a sort. */
    .attention__meta a, .feed-lag a { overflow-wrap: anywhere; }

    /* A flotta-szalag cellái ne préselődjenek olvashatatlanná. */
    .ribbon { overflow-x: auto; scrollbar-width: none; }
    .ribbon::-webkit-scrollbar { display: none; }

    /* Az idővonal ikon-oszlopa telefonon aránytalanul sok helyet vinne. */
    .timeline__time { font-size: 11px; }

    /* Hosszú, tördelhetetlen szövegek: URL-ek, útvonalak, hashek. */
    .t-mono, .rep__ip, .attention__title { overflow-wrap: anywhere; }

    /*
     * 16px minden beviteli mezőn.
     *
     * Nem esztétika: a Safari iOS-en AUTOMATIKUSAN RÁNAGYÍT minden 16px-nél
     * kisebb betűs mezőre fókuszáláskor, és utána nem nagyít vissza. Egy
     * keresőmezőbe kattintva az egész felület elcsúszik, és a felhasználó
     * kézzel csippenthet vissza. A 13,5px pont a küszöb alatt van.
     */
    .field, .attention__note, .search input, textarea, select { font-size: 16px; }

    /*
     * Ujjal is eltalálható méret.
     *
     * A ✓ gomb 28×28 volt — egérrel bőven elég, ujjal a WCAG 24px-es minimuma
     * alatt van a párnázással együtt. A teendő-lista pont az a felület, amit
     * telefonról használ az ember: megnézi a riasztást és kipipálja.
     */
    .btn--quiet { width: 40px; height: 40px; }

    /* A jegyzet és a pipa egymás alá: egy sorban a mezőnek pár karakternyi
       hely maradna a 34vw korlát miatt. */
    .attention__close { flex: 1 1 100%; flex-wrap: wrap; margin-top: var(--s3); }
    .attention__actions { flex: 1 1 100%; flex-wrap: wrap; }
    .attention__note { width: 100%; max-width: none; }
    .attention__item { flex-wrap: wrap; }
}

/*
 * NINCS globális `overflow-x: hidden`. Volt, és vissza kellett venni.
 *
 * `html { overflow-x: hidden }` mellett az iOS Safari a gyökérelemből
 * scrollportot csinál, és a layout-viewportot a TARTALOMBÓL számolja, nem a
 * kijelzőből. Az eredmény pontosan az volt, amit a hibajelentés mutatott: az
 * egész felület — a navigációs sávval együtt — a képernyő felén rendereltre
 * szűkült, a maradékot a gyökér háttere töltötte ki. A szabály nem javította
 * a túlcsordulást, csak elrejtette, és közben elrontotta a viewportot.
 *
 * A valódi megoldás az, ami mellette készült: minden széles elem a SAJÁT
 * dobozában görög — a táblák `.table-scroll`-ban, a fülsávok és a
 * flotta-szalag `overflow-x: auto`-val, a bizonyíték-blokkok `pre`-ben. Ha
 * ezek után mégis oldalra csúszik a lap, az egy konkrét elem hibája, és
 * jobb, ha látszik, mint ha egy globális szabály eltakarja.

/*
 * A legszűkebb telefonok (320–360 px).
 *
 * Itt már a márkajel is helyet visz a navigációtól, és a fejlécben úgyis ott
 * az oldal címe. A menü fontosabb, mint a logó.
 */
@media (max-width: 380px) {
    .side__brand { display: none; }
    .main { padding-inline: var(--s2); }
    .card__head, .ovbox__body, .side-box__body { padding-inline: var(--s3); }
}

/* ── Figyelt oldalak képei ───────────────────────────────────────────
   Teljes szélességű SAJÁT kártya, a hasábok fölött — ez az egyetlen
   bizonyíték a lapon, amit fél másodperc alatt ki lehet értékelni: él-e az
   oldal, és úgy néz-e ki, ahogy kell. A képek mérete viszont nem nyúlik a
   kártyával: egy teljes oldalról készült kép felülről vágva 220 pixel
   magas, kétszer olyan szélesre húzva nem informatívabb, csak nagyobb. */
.shots {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s4);
}

/* Egy SOR, mindig.

   Tördelve a devbox öt figyelt útvonala (5 × 256px) két sorba tört, és a
   második sor a kártya alján folytatódott — a `verify.urls_extra` óta ez
   valódi eset. Görgetve a képek egymás mellett maradnak, és a félig
   kilógó ötödik maga mondja meg, hogy van még. */
.shots--strip {
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    padding-bottom: var(--s2);
}

.shots--strip .shots__item { flex: 0 0 256px; scroll-snap-align: start; }

.shots__item {
    margin: 0;
    /* Rögzített szélesség: ez a kép „olvasható bélyegkép" mérete, és az
       útvonalak száma nem változtathatja meg. Zsugorodni azért tud, hogy
       egy keskeny kijelzőn se lógjon ki. */
    flex: 0 1 256px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.shots__item figcaption {
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: 12px;
    line-height: 1.5;
}

.shots__path { overflow-wrap: anywhere; color: var(--ink); }

/* ── Szerverek: egy kártya gépenként ───────────────────────────────
   A fejléc az összegzés (név, konténerek, site-szám, jelvények), alatta
   ugyanaz a ribbon, mint a flotta-oldalon — ez az, ami negyven site-nál is
   egy sor marad —, a tényadat-tábla pedig fold mögött. Sűrűség-hasadás:
   a fejléc levegős, a tábla sűrű. */
.machine { padding: var(--s5); margin-bottom: var(--s4); }
.machine__head { display: flex; align-items: flex-start; gap: var(--s4); flex-wrap: wrap; }
.machine__chip { margin-bottom: 0; flex: 0 0 auto; }
.machine__title { flex: 1 1 260px; min-width: 0; }
.machine__name { font-size: 15px; font-weight: 600; display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap; overflow-wrap: anywhere; }
.machine__addr { font-weight: 400; font-size: 12.5px; }
.machine__sub { margin-top: 2px; display: flex; flex-wrap: wrap; gap: var(--s2); font-size: 12.5px; color: var(--ink-soft); }
.machine__badges { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; margin-left: auto; }
.machine__ribbon { margin-top: var(--s4); }
.machine__facts { margin-top: var(--s3); display: flex; flex-wrap: wrap; gap: var(--s2); }
.machine__container { font-family: var(--mono); }
.machine__note { margin: var(--s3) 0 0; font-size: 12.5px; }
.machine__table { margin-top: var(--s3); }
.machine__fold { margin: var(--s4) calc(-1 * var(--s5)) calc(-1 * var(--s5)); }
.machine__fold > summary { padding-left: var(--s5); }
.machine__fold > summary::before { content: "▸"; font-size: 9px; transition: transform var(--dur-fast) var(--ease); }
.machine__fold[open] > summary::before { transform: rotate(90deg); }
.machine__fold > summary::-webkit-details-marker { display: none; }

/* ── Profil: QR a hitelesítő alkalmazáshoz ─────────────────────────
   Fehér alapon, sötét témában is: a QR-t a telefon kamerája olvassa,
   nem a szem, és a kamera a kontrasztot nézi. */
.qr { display: inline-block; padding: var(--s2); background: #fff; border-radius: var(--r); line-height: 0; }
.qr svg { width: 192px; height: 192px; display: block; }

/* ── Onboarding: mi történik éppen ────────────────────────────────
   Tizenhárom lépés valaki más szerverén, és eddig az egész csöndben telt. A
   panel az űrlap ALATT van, nem fölötte: a beküldés után az operátor szeme ott
   marad, ahol a gomb volt. */
.onb {
    margin-top: var(--s5);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface-2);
    overflow: hidden;
}

.onb__head {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s3) var(--s4);
    border-bottom: 1px solid var(--line);
}

.onb__count { margin-left: auto; font-variant-numeric: tabular-nums; font-size: 12px; }

/* A forgó jelzés a FEJLÉCBEN, egy darab: soronként pörgetve tizenhárom
   mozgó elem lenne a képernyőn, ami elvonja a figyelmet arról az egy sorról,
   ami éppen számít. */
.onb__spin {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
    border: 2px solid var(--line-strong);
    border-top-color: var(--brand);
    border-radius: 999px;
    animation: onb-spin .8s linear infinite;
}

.onb.is-done .onb__spin { animation: none; border-top-color: var(--line-strong); }

@keyframes onb-spin { to { transform: rotate(360deg); } }

/* Aki kikapcsolta a mozgást, annak ne pörögjön. A panel enélkül is működik:
   az állapotot a sorok mondják, nem a forgás. */
@media (prefers-reduced-motion: reduce) {
    .onb__spin { animation: none; }
}

.onb__steps { list-style: none; margin: 0; padding: 0; }

.onb__step {
    display: flex;
    align-items: baseline;
    gap: var(--s3);
    padding: var(--s2) var(--s4);
    border-bottom: 1px solid var(--line);
    font-size: 13px;
    color: var(--ink-faint);
}

.onb__step:last-child { border-bottom: 0; }

.onb__label { flex: 0 0 auto; }
.onb__msg   { color: var(--ink-faint); font-size: 12px; }

.onb__tag {
    margin-left: auto;
    flex: 0 0 auto;
    font-size: 11px;
    font-weight: 600;
    text-transform: lowercase;
}

/* Az állapot soha nem CSAK szín (8.7): minden sor mellett ott a szó is, a
   futóé pedig a bal szélén egy jelölt csík — színvakon és kinyomtatva is
   olvasható marad. */
.onb__step.is-running {
    color: var(--ink);
    font-weight: 550;
    background: var(--brand-tint);
    box-shadow: inset 3px 0 0 var(--brand);
}

.onb__step.is-done      { color: var(--ink-soft); }
.onb__step.is-done      .onb__tag { color: var(--ok-ink); }
.onb__step.is-skipped   .onb__tag { color: var(--ink-faint); }
.onb__step.is-blocked   { color: var(--ink); }
.onb__step.is-blocked   .onb__tag { color: var(--warning-ink); }
.onb__step.is-failed    { color: var(--ink); background: var(--critical-bg); box-shadow: inset 3px 0 0 var(--critical); }
.onb__step.is-failed    .onb__tag { color: var(--critical-ink); }
.onb__step.is-abandoned .onb__tag { color: var(--ink-faint); }

.onb__note { margin: 0; padding: var(--s3) var(--s4); font-size: 12px; border-top: 1px solid var(--line); }
