/* ============================================================================
   HMI panel — a funkció egyetlen stíluslapja.
   Betöltés: index.html (a CSS fájlok ott explicit fel vannak sorolva).
   Csak design-tokenek — így light / dark / night módban is helyes.
   ========================================================================= */

/* ----------------------------------------------------------------------------
   SZÍN-SZEREPEK  (szerep → token)

   A séma és a szerkesztő SOHA nem ismer konkrét színt, csak SZEREPET (HmiRoles.cs). Ez az a hely,
   ahol a szerep színné válik — tehát ez az EGYETLEN hely, ahol egy téma javítható, anélkül hogy
   bármelyik MENTETT panelhez hozzá kellene nyúlni.
   -------------------------------------------------------------------------- */
:root {
    --hmi-text: var(--c-text-primary);
    --hmi-muted: var(--c-text-secondary);
    --hmi-neutral: var(--c-text-muted);
    --hmi-accent: var(--c-primary);
    --hmi-ok: var(--c-success);
    --hmi-warn: var(--c-notif-warning);
    --hmi-alarm: var(--c-error);
    --hmi-inactive: var(--c-text-faint);
    --hmi-stale: var(--c-stale);
    --hmi-surface: var(--c-surface);

    /* ── RAJZI SZÍNEK — csak alakzatokra ──────────────────────────────────────────────────────
       Ezek az egyetlen NEM SZEMANTIKUS szerepek: nem azt mondják meg, hogy egy elem MIT JELENT,
       hanem hogy milyen színű legyen. Egy sémarajzon a szín MAGA a jelentés, amit a rajzoló ad neki
       (tűzivíz = piros, ivóvíz = zöld, üzemanyag = sárga) — és nincs mögötte adat, ami felülírhatná.

       ⚠️ SZÁNDÉKOSAN MÁS ÉRTÉK, mint a státusz-szerepeké. Az `alarm` a `--c-error`, ez a `red` a
       nyers `--nt-red-*` — hasonló hue, de nem ugyanaz a szín, tehát egy pirosra festett cső nem
       lesz pixelre azonos egy tüzelő riasztással. Ugyanez a `warn`/`yellow` és az `ok`/`green` pár.

       ⚠️ A PASZTELL VÁLTOZAT `color-mix`-szel készül, nem új primitívvel: a primitívek generált
       fájlból jönnek (Palette.cs, KÜLÖN REPÓ — D6), kézzel beleírni a következő generálásig tartana.
       A keverés a FELÜLET felé megy, tehát a pasztell témánként magától helyes marad. */
    --hmi-red: var(--nt-red-600);
    --hmi-green: var(--nt-green-600);
    --hmi-yellow: var(--nt-amber-600);

    --hmi-red-pale: color-mix(in srgb, var(--nt-red-500) 30%, var(--c-surface));
    --hmi-green-pale: color-mix(in srgb, var(--nt-green-400) 32%, var(--c-surface));
    --hmi-yellow-pale: color-mix(in srgb, var(--nt-amber-400) 34%, var(--c-surface));
}

/* ⚠️ A night téma NEM javítható pusztán azzal, hogy tokent használunk — épp ellenkezőleg.
   Két ütközése van, mindkettő biztonsági kérdés egy HMI-n:
     • a --c-primary (márkaszín) night módban BÍBORVÖRÖS, tehát egy „hangsúlyos" elem riasztásnak
       olvasódna;
     • a --c-text-primary és a --c-error night módban UGYANAZ a vörös, tehát a riasztás nem üt ki a
       normál szövegből.
   Ezért itt a szerepek nem hue-ban, hanem FÉNYERŐBEN válnak szét: a riasztás a legvilágosabb, alatta
   a hangsúly, majd a szöveg, végül a halvány. A zöld „rendben" marad az egyetlen hue-eltérés — a
   night paletta zöldje eleve tompított, nem rontja a sötétadaptációt. */
:root[data-theme="night"] {
    --hmi-alarm: var(--c-notif-ongoing-critical);
    --hmi-accent: var(--c-text-primary);
    --hmi-text: var(--c-text-secondary);
    --hmi-muted: var(--c-text-muted);
    --hmi-neutral: var(--c-text-muted);

    /* ⚠️ A RAJZI SZÍNEK NIGHT MÓDBAN NEM SZÍNEK, HANEM FÉNYERŐ-FOKOZATOK. Egy zöld vagy sárga folt
       a képernyőn tönkreteszi a sötétadaptációt — pontosan azt, amiért a night mód van. A rajzoló
       szándéka (három megkülönböztethető „festék") viszont megmarad: három, egymástól jól elváló
       világosságú vörös. Ugyanaz az elv, ami a többi szerepet is hue helyett fényerőben választja
       szét itt.
       A pasztell változatok ennek megfelelően SÖTÉTEBB vörösek, nem világos foltok. */
    --hmi-red: var(--c-notif-ongoing-critical);
    --hmi-green: var(--c-text-secondary);
    --hmi-yellow: var(--c-text-primary);

    --hmi-red-pale: color-mix(in srgb, var(--c-notif-ongoing-critical) 30%, var(--c-surface));
    --hmi-green-pale: color-mix(in srgb, var(--c-text-secondary) 30%, var(--c-surface));
    --hmi-yellow-pale: color-mix(in srgb, var(--c-text-primary) 30%, var(--c-surface));
}

/* ----------------------------------------------------------------------------
   LAP-FEJLÉC (lista képernyő)
   -------------------------------------------------------------------------- */
.hmi-page__head {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
    margin-bottom: .9rem;
}

.hmi-page__title {
    margin: 0;
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--text-clr);
}

.hmi-page__spacer {
    flex: 1 1 auto;
}

.hmi-page__toggle {
    font-size: .85rem;
    font-style: italic;
    color: var(--muted-clr);
    cursor: pointer;
    user-select: none;
}

/* ----------------------------------------------------------------------------
   PANELLISTA
   -------------------------------------------------------------------------- */
/* ⚠️ 260px a minimum, nem 240. Az `auto-fill` minél szélesebb az ablak, annál TÖBB és annál
   KESKENYEBB oszlopot rak ki (a maradékot elosztja) — egy 2400px széles képernyőn a kártya
   keskenyebb lesz, mint egy 1200px-esen. A lábléc szövege + három ikon-gomb ~250px alatt kezd
   szorulni; a tördelés innentől már megfogja, de fölöslegesen ne is kerüljön oda. */
.hmi-list {
    display: grid;
    gap: 14px;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}

/* ⚠️ A kártya <div>, NEM <button>: a láblécben műveleti gombok vannak, gomb pedig nem ágyazható
   gombba. A megnyitás a belső .hmi-card__open gomb dolga, a lábléc gombjai annak testvérei. */
.hmi-card {
    display: grid;
    grid-template-rows: 1fr auto;
    padding: 0;
    font: inherit;
    text-align: left;
    color: var(--text-clr);
    background: var(--list-bg);
    border: var(--border-thin);
    border-radius: var(--list-border-radius);
    overflow: hidden;
    transition: border-color .16s, box-shadow .16s, transform .16s;
}

.hmi-card:hover {
    border-color: var(--primary-clr);
    box-shadow: var(--elev-hover);
    transform: translateY(-2px);
}

/* A kártya megnyitó felülete: miniatűr + törzs. Gomb, hogy billentyűzetről is elérhető legyen. */
.hmi-card__open {
    display: grid;
    grid-template-rows: auto 1fr;
    padding: 0;
    font: inherit;
    text-align: left;
    color: inherit;
    background: none;
    border: 0;
    cursor: pointer;
}

.hmi-card__open:focus-visible,
.hmi-card--ghost:focus-visible {
    outline: 2px solid var(--primary-clr);
    /* Befelé, mert a kártya overflow:hidden — kifelé rajzolt körvonalat levágná. */
    outline-offset: -3px;
}

.hmi-card__thumb {
    display: grid;
    place-items: center;
    background: var(--c-surface-elevated);
    border-bottom: var(--border-thin);
    overflow: hidden;

    /* ⚠️ SAJÁT MAGASSÁG KELL. Korábban a képarányt a kártya markupja adta INLINE
       (`style="aspect-ratio: vászon-szélesség / magasság"`), mert a doboz tartalma egy teljes
       vászon-render vagy egy háttérkép volt. A miniatűr megszűnt (a lista csak metaadatot kap a
       szerverről), tehát a dobozban már csak a semleges ikon áll — arányszám nélkül a sáv az ikon
       magasságára lapulna össze, és a kártyák teteje ránézésre elromlottnak látszana.

       Fix magasság, nem képarány: a vászon mérete a csomagban van, amit itt nem is ismerünk. Elég
       ahhoz, hogy a kártyának legyen felismerhető feje, de nem foglal el egy egész bélyegképnyi
       helyet olyan tartalomért, ami nincs. */
    min-height: 64px;

    /* ⚠️ POZICIONÁLT SZÜLŐ. Ma nincs benne abszolút pozicionált gyerek, de a `.hmi-canvas`
       (`position: absolute; inset: 0`) pozicionált ős nélkül a LEGKÖZELEBBI ilyenre ugrik ki, és a
       lap méretében, mindenre ráterülve rajzolódik ki — az `overflow: hidden` ilyenkor nem véd.
       Marad, hogy ha ide bármikor visszakerül vászon, ne ez legyen az első hibajelenség. */
    position: relative;
}

/* A háttérkép mint miniatűr. `cover`, nem `contain`: a kártya képaránya a vászoné, tehát a kettő
   gyakorlatilag egyezik — a `cover` viszont akkor sem hagy üres csíkot, ha egy kézzel szerkesztett
   panelen mégis eltérnek. */
/* LETILTOTT panel: halvány, de olvasható és megnyitható — ugyanaz a bánásmód, amit a riasztás- és
   automatizálás-lista ad a letiltott soroknak. Elrejtve a felhasználó azt hinné, törölték.
   A lábléc gombjai KIMARADNAK a halványításból: épp azokkal lehet visszakapcsolni. */
.hmi-card--disabled .hmi-card__open {
    opacity: .55;
}

/* Jelvények a név és a leírás alatt: a szerver metaadata, tehát MINDIG igaz — ez az, amit a miniatűr
   helyett kapunk. Sorrendjük a markupé (alapértelmezett · letiltva · vezérlés).

   ⚠️ A TÖRZS ALJÁRA TAPADNAK (`margin-top: auto`), nem a leírás alá. A panelek leírása tetszőleges
   hosszú — vagy nincs is —, tehát a szöveg után rakva a jelvények kártyánként más magasságban
   állnának: az egyiken a cím alatt, a másikon három sorral lejjebb. Alulra húzva viszont a rács
   egy sorában MINDIG egy vonalban vannak (a rács-elemek egyforma magasra nyúlnak), és a szem
   egyetlen pásztázással végigolvassa őket.

   A `padding-top` adja a MINIMUM távolságot a szövegtől — a `margin-top: auto` a korábbi
   `margin-top: 6px`-et amúgy is felülírná. */
.hmi-card__badges {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: auto;
    padding-top: 6px;
}

/* JELVÉNY a listakártyán: ikon + nagybetűs mikroszöveg egy tónusos chipben.

   ⚠️ EGYETLEN definíció — korábban KETTŐ volt ugyanerre az osztályra, nyolcvan sorral odébb, és a
   második a legtöbb tulajdonságot felül is írta. Ami maradt az elsőből (keret, sortávolság, nowrap),
   az némán keveredett a másodikkal: a „letiltva" jelvény például a KÉK tónuson viselt NARANCS keretet.

   `inline-flex`, mert a szöveg elé ikon került: enélkül az ikon alapvonalra ülne, és a chip
   magassága a sorköz szerint ugrálna. */
.hmi-badge {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 2px 7px;
    border-radius: 5px;
    /* ⚠️ ÁTLÁTSZÓ KERET, nem `border: 0`: csak az alapértelmezett jelvény visel látható kontúrt, a
       méretét viszont mindhármuknak egyformán kell adni — enélkül az egyik chip két képponttal
       magasabb lenne a szomszédainál. */
    border: var(--border-thin);
    border-color: transparent;
    background: color-mix(in srgb, var(--primary-clr) 18%, transparent);
    color: var(--primary-clr);
    font-size: .65rem;
    font-weight: 700;
    line-height: 1.6;
    letter-spacing: .04em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* Az ALAPÉRTELMEZETT a pozitív kiemelés: egy hajón egy van, és azt érdemes elsőre megtalálni. Ezért
   ez az EGYETLEN jelvény, ami keretet is kap — a tónus önmagában nem emelné ki a szomszédai közül. */
.hmi-badge--default {
    border-color: var(--primary-clr);
}

/* A LETILTVA nem hiba, csak állapot — figyelmeztető tónus, nem piros.
   ⚠️ KERET NÉLKÜL (felhasználói kérés): a narancs kontúr riasztásnak látszott egy olyan jelvényen,
   ami csak annyit mond, hogy ez a panel kimarad a vetítésből. A tónus és az ikon elég. */
.hmi-badge--off {
    background: color-mix(in srgb, var(--warning-clr) 18%, transparent);
    color: var(--warning-clr);
}

/* A kártya törzse. FLEX OSZLOP, és ez nem díszítés: ettől tud a jelvény-sor az aljára tapadni
   (`margin-top: auto`), a név és a leírás pedig a tetején maradni. A törzs a `.hmi-card__open`
   rácsában `1fr`-t kap, tehát a rendelkezésre álló magasságot végig kitölti — enélkül az „aljára"
   a szöveg alját jelentené, nem a kártyáét. */
.hmi-card__bd {
    display: flex;
    flex-direction: column;
    padding: 10px 14px;
}

.hmi-card__name {
    display: block;
    font-weight: 600;
    font-size: .95rem;
}

.hmi-card__desc {
    display: block;
    margin-top: 2px;
    font-size: .78rem;
    line-height: 1.35;
    color: var(--muted-clr);
}

/* ⚠️ `flex-wrap: wrap` — enélkül a lábléc KILÓG a kártyából. A flex-elemek alapértelmezett
   `min-width: auto`-ja miatt a szövegblokk nem tud a saját min-content szélessége alá zsugorodni,
   a három ikon-gomb pedig fix széles: ha a kettő együtt nem fér el, a különbség egyszerűen kifolyik
   a kártya széle mellett. És minél SZÉLESEBB a képernyő, annál gyakoribb — a rács `auto-fill`-je
   ilyenkor több és keskenyebb oszlopot rak ki. */
/* A LÁBLÉC KÉT SOR: fent a meta (mikor · ki), alatta a műveletek. Nem tördelés, hanem VALÓDI két sor.

   ⚠️ EZ VÁLTOTTA LE AZ EGYSOROS, TÖRDELHETŐ VÁLTOZATOT, és a különbség nem esztétikai. Egy sorban a
   szöveg és a gombok ugyanazért a helyért versengtek: keskeny kártyán (a rács `auto-fill`-je széles
   képernyőn TÖBB és KESKENYEBB oszlopot rak ki) a dátum második sorba tört, a gombok pedig a
   megnőtt sávban függőlegesen középre álltak — így csúsztak látszólag „feljebb". Külön sorokkal a
   két tartalom magassága már nem hat egymásra. */
.hmi-card__ft {
    display: flex;
    flex-direction: column;
    gap: 2px;
    /* Oldalt kevesebb, mint a kártya törzsében: az alsó sor két végén ikon-gombok állnak, azoknak
       saját belső paddingjük van. A meta a maga 8px-ével áll be a törzs 14px-es vonalára. */
    padding: 4px 6px 5px;
    border-top: var(--border-thin);
    font-size: .75rem;
    color: var(--muted-clr);
    font-variant-numeric: tabular-nums;
}

/* MIKOR (balra) · KI (jobbra). A `baseline` igazítás azért kell, mert a két szöveg más tónusú és
   más hosszú — középre igazítva egy hajszálnyit elcsúsznának egymáshoz képest. */
.hmi-card__meta {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    min-width: 0;
    padding: 0 8px;
}

/* A szerkesztő e-mail-címe: a jobb szélen zár, és HOSSZÚ. Nem törhet ketté (a dátummal egy sorban
   marad), tehát a maradék helyre kell metszeni — enélkül egy hosszú cím kifolyna a kártyából. */
.hmi-card__date {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--extra-muted-clr);
}

/* MŰVELET-SOR: balra a sorrend-nyilak, jobbra a panel-műveletek — a két végre húzva, mert a kettő
   MÁS FAJTA művelet (lásd a markup jegyzetét). */
.hmi-card__ops {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
}

/* A csoportokon belül a gombok szorosan, hogy a két csoport önálló egységként olvasódjon. */
.hmi-card__move,
.hmi-card__acts {
    display: flex;
    align-items: center;
    gap: 2px;
}

/* „Új panel" csempe a lista végén. MEGTARTJA a kártya-hátteret, csak a kerete szaggatott — így
   egyértelműen kártya marad, nem eltűnő üres folt. A keret ezért nem a nagyon halvány --c-divider,
   hanem a látható --extra-muted-clr. */
.hmi-card--ghost {
    display: grid;
    /* Felülírja a .hmi-card sorrácsát — itt a három sor középre zsúfolva kell. */
    grid-template-rows: none;
    place-items: center;
    align-content: center;
    gap: 3px;
    min-height: 200px;
    padding: 20px;
    text-align: center;
    color: var(--muted-clr);
    border: 1px dashed var(--extra-muted-clr);
    cursor: pointer;
}

.hmi-card--ghost:hover {
    /* Már valódi gomb, tehát kap valódi hover-választ — csak a keret marad szaggatott. */
    border-color: var(--primary-clr);
    color: var(--text-clr);
}

.hmi-card--ghost .hmi-card__name {
    color: var(--muted-clr);
}

/* ÜRES ÁLLAPOT: nincs egyetlen panel sem. A rács HELYETT jelenik meg (nem benne), ezért nem kell
   `grid-column: 1 / -1` — a rácsba téve egyetlen ~260 px-es oszlopba szorult volna, balra tolva.

   A geometria a ghost csempéé (középre zsúfolt sorok), csak nagyobb levegővel: itt ez a lap teljes
   tartalma, nem egy csempe a többi közt. Keret és háttér SZÁNDÉKOSAN nincs: nincs mit körbekeretezni,
   a szöveg és a két gomb magában áll. */
.hmi-list__empty {
    display: grid;
    place-items: center;
    align-content: center;
    gap: .35rem;
    min-height: 260px;
    padding: 2rem 1rem;
    text-align: center;
}

/* ⚠️ Külön szabály a <p>-re: az app.css a div/span/p-re KÖZVETLENÜL erőlteti a --text-clr-t, tehát a
   szülőn beállított halvány szín nem öröklődne le. */
.hmi-list__empty-title {
    margin: 0;
    color: var(--muted-clr);
}

/* A cselekvésre hívó sor. --muted-clr és nem --extra-muted-clr: ez viszi tovább a felhasználót, tehát
   olvashatónak kell lennie — ugyanaz a döntés, mint az inspektor üres állapotában. */
.hmi-list__empty-hint {
    margin: 0 0 .6rem;
    font-size: .85rem;
    font-style: italic;
    color: var(--muted-clr);
}

/* Két művelet egy sorban, de TÖRDELHETŐ: keskeny ablakban egymás alá kerülnek, nem folynak ki. */
.hmi-list__empty-acts {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .5rem;
}

/* ----------------------------------------------------------------------------
   MEGNYITOTT PANEL
   -------------------------------------------------------------------------- */

/* Fejléc-sorrend: vissza-gomb (jobb szélén a frissesség-jelzés), alatta a név, majd a leírás. */
.hmi-view__back {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
    margin-bottom: .85rem;
}

/* Frissesség-jelzés. Halkan ül a fejlécben — nem riasztás, hanem tájékoztatás; elavult adatnál
   viszont felveszi ugyanazt a `stale` szerepet, amit a vásznon lévő értékek is, hogy a kettő
   ugyanazt mondja. */
.hmi-fresh {
    display: inline-flex;
    align-items: baseline;
    gap: .4rem;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: .75rem;
    color: var(--muted-clr);
    background: var(--c-surface-elevated);
    border: 1px solid var(--c-border-subtle);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.hmi-fresh--stale {
    color: var(--hmi-stale);
    border-color: color-mix(in srgb, var(--hmi-stale) 45%, transparent);
}

.hmi-fresh__stale {
    font-weight: 600;
}

/* Az olvasat nélküliek száma HALVÁNYABB, mint az elavultaké — és ez nem díszítés. Az elavult adat
   azt jelenti, hogy amit látsz, az RÉGI (tehát félrevezethet); az olvasat nélküli csak annyit, hogy
   ott most nincs mit mutatni. A második kevésbé veszélyes, tehát kevésbé is hangsúlyos. */
.hmi-fresh__nodata {
    opacity: .75;
}

.hmi-view__name {
    margin: 0;
    font-size: 1.25rem;
    line-height: 1.25;
    font-weight: 700;
    color: var(--text-clr);
}

/* Név és leírás szerkesztéskor. UGYANAZT a helyet és sorrendet foglalja, mint a megjelenítő alak
   (név felül a kapcsoló mellett, alatta a leírás), hogy a szerkesztésbe lépés ne rendezze át a fejlécet.
   ⚠️ NINCS `max-width` rajtuk — az a KONTÉNEREN van (.hmi-shell__meta). Az elemre tett `ch`-alapú
   korlát az elem SAJÁT betűméretéhez igazodik, tehát egy 1.25rem-es névmezőn és egy 0.85rem-es
   leíráson ugyanaz a `78ch` két különböző pixelérték lenne — pont az egyformaságot rontaná el. */
.hmi-view__input {
    width: 100%;
    min-width: 0;
    padding: .3rem .6rem;
    font: inherit;
    color: var(--text-clr);
    background: var(--list-bg);
    border: 1px solid var(--c-border-subtle);
    border-radius: 8px;
}

/* A h2 tipográfiáját veszi át, hogy a váltás ne ugrasson a soron. */
.hmi-view__input--name {
    font-size: 1.25rem;
    line-height: 1.25;
    font-weight: 700;
}

/* A megjelenítő alak (.hmi-view__desc) tipográfiáját veszi át, hogy a módváltás ne ugrasson a soron. */
.hmi-view__input--desc {
    font-size: .85rem;
    line-height: 1.4;
}

.hmi-view__input:focus {
    outline: 2px solid var(--primary-clr);
    outline-offset: -1px;
}

/* A margót a rács `row-gap`-je adja, ezért itt nincs — különben duplázna.
   A szélességkorlát is elmarad, hogy a nézet és a szerkesztés között ne ugorjon a leírás sávja. */
.hmi-view__desc {
    margin: 0;
    font-size: .85rem;
    line-height: 1.4;
    color: var(--muted-clr);
}

/* Elavult mélylink (törölt vagy nem létező panel-azonosító), betöltés és betöltés-hiba. */
.hmi-view__notfound {
    padding: 2rem;
    text-align: center;
    color: var(--muted-clr);
}

/* A panel HELYETT megjelenő állapot-blokk: üzenet, és ha van mit tenni, egy gomb alatta.
   Középre zárt oszlop, mert nincs mellette tartalom, amihez igazodni kellene. */
.hmi-view__state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .25rem;
    padding-bottom: 2rem;
}

/* Másodlagos, figyelmeztető mondat egy dialógus törzsében — ma a piszkozat-visszatöltésnél az, hogy
   mely képek nem voltak pótolhatók. Halkabb, mint a fő kérdés: nem a döntés tárgya, hanem a
   következménye, amit tudnia kell hozzá. */
.hmi-dialog__warn {
    margin-top: .75rem;
    font-size: .875rem;
    color: var(--warning-clr);
}

/* MENTETLEN jelzés a szerkesztő fejlécében, a Mégse/Mentés páros előtt. Halkan: az ÁLLAPOTOT mondja
   ki, nem cselekvésre szólít — a cselekvés a mellette álló elsődleges gomb. */
.hmi-shell__dirty {
    font-size: .8rem;
    color: var(--muted-clr);
    white-space: nowrap;
    align-self: center;
}

/* ----------------------------------------------------------------------------
   A SZERKESZTŐ BLOKK

   ⚠️ EGYETLEN keretes felület, nem több lebegő kártya. Korábban a fejléc, az eszköztár, a vászon és
   az oldalsáv mind saját kerettel és árnyékkal állt a lapon, és nem látszott, hogy ugyanannak az
   eszköznek a részei. A belső tagolást innentől HAJSZÁLVONAL adja.

   Következmény: a „szerkesztő módban vagy" jelzést egyedül a blokk KÜLSŐ kék kerete viszi. A belső
   részek (eszközsáv, vászon, inspektor) ezért NEM kapnak külön kék keretet — négy egymásba ágyazott
   kék vonal nem négyszer akkora hangsúly, hanem zaj.
   -------------------------------------------------------------------------- */
.hmi-shell {
    display: flex;
    flex-direction: column;
    background: var(--list-bg);
    border: var(--border-thin);
    border-radius: var(--list-border-radius);
    /* A belső részek (munkaterület-háttér, oldalsáv-elválasztók) így követik a lekerekített sarkot. */
    overflow: hidden;
    /* A teljes képernyős vetítés-rátét (.hmi-fsnav) EHHEZ képest helyezkedik el — a blokk a teljes
       képernyős elem, tehát a rátétnek is ez a vonatkoztatási kerete. */
    position: relative;
}

.hmi-shell--edit {
    border-color: var(--primary-clr);
}

/* TELJES KÉPERNYŐ — csak néző módban.

   A célpont a BLOKK, nem a vászon: a fejléc (panel neve + nézőpont-vezérlők) és a sávok (offline,
   hiányzó kötések) egy fal-kijelzőn a legfontosabbak közé tartoznak. Csak a vásznat kirakva a
   „nincs kapcsolat” figyelmeztetés épp abban a nézetben tűnne el, amit órákig néznek.

   ⚠️ HÁTTERET FESTENI KELL. A böngésző a teljes képernyős elem mögé FEKETÉT tesz, a blokk háttere
   pedig eddig a lapé volt — az áttetsző részeken (a munkaterület körül) így fekete ütne át. Dark
   témában észrevétlen, világosban viszont fekete keret. */
.hmi-shell--fs {
    height: 100%;
    background: var(--list-bg);
    border: 0;
    border-radius: 0;
}

/* A törzs és a munkaterület kitölti a maradék magasságot. Nézetben a törzsnek egyetlen gyereke van
   (a munkaterület), ezért itt flexre váltunk: a rács is meg tudná oldani, de így a lánc a
   nézőkeretig egyetlen, végigkövethető szabálysor. */
.hmi-shell--fs .hmi-shell__body {
    display: flex;
    flex: 1;
    min-height: 0;
}

.hmi-shell--fs .hmi-shell__work {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

/* A nézőkeret kapja a teljes maradékot, a vászon pedig függőlegesen középre kerül benne.

   ⚠️ A MÉRETEZÉS MODELLJE NEM VÁLTOZIK: a stage szélessége továbbra is nagyítás × 100%. „Illeszd a
   képernyőre” (contain) szabály azért NINCS, mert az a szélességet és a magasságot egyszerre kötné
   meg, ami az `aspect-ratio`-val ütközik. A panelek pedig szinte kivétel nélkül fekvők (az
   alapértelmezés 16:9, a háttérfotók is azok), tehát a szélesség-vezérelt illesztés a gyakorlatban
   mindig kitölti a képernyőt; egy szokatlanul magas panelnél a válasz a kicsinyítés.

   ⚠️ `safe center`: sima `center` mellett egy a keretnél magasabb vászon FELSŐ része elérhetetlen
   lenne — a középre igazítás a tartalmat mindkét irányban túllógatja, és a görgetés a negatív
   irányba nem megy. Ha egy böngésző nem ismeri a kulcsszót, az egész deklarációt eldobja, és
   `flex-start` marad: a panel felülre kerül, ami rossz esetnek is elfogadható. */
.hmi-shell--fs .hmi-work__view {
    flex: 1;
    min-height: 0;
    max-height: none;
    overflow: auto;
    display: flex;
    flex-direction: column;
    justify-content: safe center;
}

/* ⚠️ `flex: none` — KÖTELEZŐ. Oszlop-irányú flexben a főtengely a függőleges, tehát a stage
   alapértelmezett `flex-shrink: 1`-e a MAGASSÁGÁT nyomná össze, amint a vászon magasabb a keretnél:
   az `aspect-ratio` felborulna, és a rajz torzulva jelenne meg görgetés helyett. */
.hmi-shell--fs .hmi-work__view > .hmi-stage {
    flex: none;
}

/* A leírás teljes képernyőn kimarad: prózai kísérőszöveg, amit egy fal-kijelzőn senki nem olvas,
   viszont a vászontól venne el magasságot. A NÉV marad — az mondja meg, melyik panelt nézed. */
.hmi-shell--fs .hmi-view__desc {
    display: none;
}

/* FEJLÉC: balra a név és a leírás, jobbra a mód-műveletek. */
.hmi-shell__head {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    flex-wrap: wrap;
    padding: .6rem .75rem;
    border-bottom: 1px solid var(--c-border-subtle);
}

/* Név és leírás egymás alatt, KÖZÖS szélességkorláttal a konténeren.

   A korlát azért itt van és nem a mezőkön, mert így egyetlen érték vonatkozik mindkettőre — a mezők
   `width: 100%`-kal töltik ki. Ha a korlát a mezőkön ülne `ch`-ban, a két eltérő betűméret két
   különböző pixelszélességet adna. Széles ablakban tehát korlátozott, keskenyben kitölti a helyet. */
.hmi-shell__meta {
    display: flex;
    flex-direction: column;
    gap: .25rem;
    /* Nő, ha van hely, de 18rem alá szorulva inkább tördeljen a sor, és a gombok essenek alá. */
    flex: 1 1 18rem;
    min-width: 0;
    max-width: 40rem;
}

/* A mód-műveletek (HmiToolbar gyökere). `margin-left: auto` és nem távtartó elem: tördeléskor a
   gombsor így a saját sorának BAL szélére kerül, nem lóg be a semmibe. */
.hmi-shell__acts {
    display: flex;
    align-items: center;
    gap: .4rem;
    flex-wrap: wrap;
    margin-left: auto;
}

/* Függőleges elválasztó egy gombsoron belül (visszavonás | kilépés, fájl | panel).
   ⚠️ --extra-muted-clr és NEM --c-border-subtle: az utóbbi 8%-os áttetsző fekete/fehér, ami a kártya
   hátterén gyakorlatilag nem látszik — a csoporthatár pedig pont attól határ, hogy látod. */
.hmi-shell__sep {
    align-self: stretch;
    width: 1px;
    min-height: 1.5rem;
    margin-inline: .25rem;
    background: var(--extra-muted-clr);
}

/* ESZKÖZSÁV a vászon fölött (csak szerkesztéskor). */
.hmi-editbar {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
    padding: .4rem .75rem;
    border-bottom: 1px solid var(--c-border-subtle);
}

.hmi-editbar__spacer {
    flex: 1 1 auto;
}

.hmi-editbar__snap {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
}

/* Kijelölés-kijelző. */
.hmi-editbar__sel {
    display: inline-flex;
    align-items: baseline;
    gap: .4rem;
    font-size: .8rem;
    color: var(--muted-clr);
}

.hmi-editbar__selkind {
    font-weight: 600;
    color: var(--primary-clr);
}

.hmi-editbar__selid {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .75rem;
}

/* ESZKÖZVÁLTÓ (kijelölés / mozgatás).

   ⚠️ SZEGMENTÁLT VEZÉRLŐ, nem két különálló ikon-gomb. A különbség nem esztétikai: itt MINDIG
   pontosan az egyik mód aktív (vagy kijelölsz, vagy a nézetet mozgatod) — nincs „egyik sincs"
   állapot. Két magában álló gomb ezt nem mondja el; egy közös, süllyesztett sávban ülő pár viszont
   már a formájával azt jelenti, hogy a kettő EGY választás két állása.

   Ezért a kiválasztás HÁROM csatornán látszik, nem egyen: (1) a csoport süllyesztett sávjából
   kiemelkedő tömör kitöltés, (2) a keret, (3) a glifa ellentétes színe. Egy operátori felületen
   ugyanaz a szabály, mint a vásznon: az állapot ne múljon egyetlen árnyalaton — night módban a
   szerepek fényerőben válnak szét, nem hue-ban. */
.hmi-tools {
    display: inline-flex;
    gap: 2px;
    padding: 2px;
    background: var(--c-surface-elevated);
    border: 1px solid var(--c-border-subtle);
    border-radius: 8px;
}

.hmi-tool {
    display: grid;
    place-items: center;
    width: 28px;
    height: 26px;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
}

.hmi-tool:hover {
    background: color-mix(in srgb, var(--primary-clr) 14%, transparent);
}

.hmi-tool:focus-visible {
    outline: 2px solid var(--primary-clr);
    outline-offset: -2px;
}

/* ⚠️ A csoporttal EGYÜTT írva (`.hmi-tools .hmi-tool--on`), és a `:hover` változattal is.
   Két oka van, és mindkettő valódi:
     • fajsúly: a `.hmi-tool:hover` (0,2,0) NAGYOBB, mint a sima `.hmi-tool--on` (0,1,0) — a hover
       tehát felülütné a kitöltést, és a sorrend ezen nem segít. Márpedig kattintás után az egér
       pontosan a gombon marad: abban az egyetlen pillanatban tűnne el a visszajelzés, amikor a
       felhasználó keresi;
     • a csoport-előtaggal a szabály (0,2,0)/(0,3,0) fajsúlyú, tehát semmilyen máshonnan érkező
       egyosztályos gombstílus (bootstrap-reset, app.css) nem tudja némán elnyelni. */
.hmi-tools .hmi-tool--on,
.hmi-tools .hmi-tool--on:hover {
    background: var(--primary-clr);
    box-shadow: 0 0 0 1px var(--primary-clr);
}

.hmi-tool__ico {
    width: 17px;
    height: 17px;
    fill: none;
    stroke: var(--muted-clr);
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.hmi-tool__f {
    fill: var(--muted-clr);
    stroke: none;
}

.hmi-tool:hover .hmi-tool__ico {
    stroke: var(--primary-clr);
}

.hmi-tool:hover .hmi-tool__f {
    fill: var(--primary-clr);
}

.hmi-tools .hmi-tool--on .hmi-tool__ico,
.hmi-tools .hmi-tool--on:hover .hmi-tool__ico {
    stroke: var(--c-on-primary, #fff);
}

.hmi-tools .hmi-tool--on .hmi-tool__f,
.hmi-tools .hmi-tool--on:hover .hmi-tool__f {
    fill: var(--c-on-primary, #fff);
}

/* VETÍTÉS-INDÍTÓ a panellista fejlécében: [⏱ ütem] [▶ Vetítés].

   A két vezérlő EGY csoport, mert egy műveletről szólnak — az ütem a gomb paramétere, nem önálló
   beállítás. Ezért ülnek közös dobozban, és ezért kap a doboz (nem a gomb) egérrá-érésre előtöltést. */
.hmi-present {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
}

.hmi-present__timer {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    cursor: pointer;
}

/* A natív legördülő, a szomszéd gomb magasságára húzva. Saját keret nélkül: kerettel a fejléc egy
   űrlapra kezdene hasonlítani, holott itt egyetlen művelet paramétere áll. */
.hmi-present__sel {
    height: 32px;
    max-width: 7rem;
    padding: 0 .2rem;
    font: inherit;
    font-size: .85rem;
    color: var(--muted-clr);
    background: none;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
}

.hmi-present__sel:hover {
    color: var(--primary-clr);
}

.hmi-present__sel:focus-visible {
    outline: 2px solid var(--primary-clr);
    outline-offset: -2px;
}

/* ⚠️ A LEGÖRDÜLŐ TÉTELEI a RENDSZER felületén jelennek meg, nem a lapon: ott a `background: none`
   átlátszót jelentene, sötét témában fehér alapon fehér szöveget. A színt tehát ki KELL mondani. */
.hmi-present__sel option {
    color: var(--text-clr);
    background: var(--c-surface, var(--list-bg));
}

/* A VETÍTÉS HOSTJA — a teljes képernyős réteg elemje, a panellistán.

   ⚠️⚠️ ZÁRVA IS A DOM-BAN VAN, ÉS EZ A LÉNYEG: a `requestFullscreen` létező elemet kíván, ráadásul
   a kattintás pillanatában — ezért a host nem tűnhet el, csak összemegy. `display: none` NEM
   használható (nem renderelt elemet a böngésző nem tesz teljes képernyőre), ezért nulla méret +
   `overflow: hidden`: a fában ott van, a lapon nem foglal semmit.

   Teljes képernyőn viszont a böngésző maga méretezi a képernyőre, tehát a `hmi-shell--fs` blokk
   pontosan úgy tölti ki, mint a panel-oldalon. */
.hmi-present-host {
    width: 0;
    height: 0;
    overflow: hidden;
    background: var(--list-bg);
}

/* FUTÓ VETÍTÉS. A réteg akkor is kitölti a nézetablakot, ha a teljes képernyő kérése ELUTASÍTVA lett
   (beágyazás, házirend) — így a vetítés sosem lesz „láthatatlanul futó": legfeljebb a böngésző saját
   sávjai maradnak fölötte. Teljes képernyőn ugyanez a végeredmény, csak a böngésző méretez. */
.hmi-present-host--on {
    position: fixed;
    inset: 0;
    width: auto;
    height: auto;
    z-index: 5000;
}

/* Töltés-felirat a vetítés első képén (a csomag még úton). Teljes képernyőn állunk, tehát középre. */
.hmi-present-wait {
    display: grid;
    place-items: center;
    height: 100%;
    color: var(--muted-clr);
    font-size: 1rem;
}

/* ----------------------------------------------------------------------------
   VETÍTÉS-RÁTÉT (teljes képernyőn)

   ⚠️ A KÉP FÖLÖTT ÁLL, NEM MELLETTE. Teljes képernyőn minden képpont a panelé; egy állandó gombsor
   a vászon magasságából venne el. A rátét ezért `absolute`, a blokk teljes területén — de csak a
   gombok fogják az egeret (`pointer-events`), tehát a panel maga végig kattintható marad (kapcsolók,
   hover-popup).

   ⚠️ A SZÍNEIT NEM A TÉMA ADJA. A panel alatt fotó is lehet, ahol sem a világos, sem a sötét téma
   felületszíne nem garantál kontrasztot — ezért fix, áttetsző fekete korong fehér nyíllal, ahogy
   minden képnézegetőben. Ez az egyetlen hely a HMI-ben, ahol a szín nem token.
   -------------------------------------------------------------------------- */
.hmi-fsnav {
    position: absolute;
    inset: 0;
    z-index: 4;
    pointer-events: none;
    opacity: 1;
    transition: opacity .25s ease;
}

/* TÉTLENSÉGRE ELTŰNIK, az egérmutatóval együtt. Az osztályt a `hmi-fullscreen.js` teszi a blokkra
   minden mutató-mozdulat után újraindított időzítővel — CSS-ből ezt nem lehet kifejezni (a :hover
   egy mozdulatlanul a panel fölött felejtett egérnél is örökre igaz maradna). */
.hmi-shell--idle .hmi-fsnav {
    opacity: 0;
}

.hmi-shell--idle {
    cursor: none;
}

.hmi-fsnav__btn {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    padding: 0;
    color: #fff;
    background: rgba(0, 0, 0, .45);
    border: 1px solid rgba(255, 255, 255, .25);
    border-radius: 50%;
    cursor: pointer;
    /* A rátét maga átereszti az egeret, a gombok viszont nem — különben a panel nyelné el a kattintást. */
    pointer-events: auto;
}

.hmi-fsnav__btn:hover {
    background: rgba(0, 0, 0, .65);
}

.hmi-fsnav__btn:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 2px;
}

.hmi-fsnav__btn--prev {
    left: 1.25rem;
}

.hmi-fsnav__btn--next {
    right: 1.25rem;
}

/* A KILÉPÉS a jobb FELSŐ sarokban — ahol minden teljes képernyős nézetben, és jó messze a
   „következő" nyíltól: az egyiket ismételgetve nyomkodják, a másikkal véget vetnek a vetítésnek. */
.hmi-fsnav__btn--close {
    top: 1.25rem;
    right: 1.25rem;
    width: 44px;
    height: 44px;
    transform: none;
}

/* HÁNYADIK — alul középen, ahol a dia-számlálók szoktak. Nem gomb: nincs mit tenni vele. */
.hmi-fsnav__pos {
    position: absolute;
    bottom: 1.25rem;
    left: 50%;
    transform: translateX(-50%);
    padding: .2rem .7rem;
    font-size: .85rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: #fff;
    background: rgba(0, 0, 0, .45);
    border-radius: 999px;
}

/* NAGYÍTÁS: [−] [100%] [+] egy egységként. A számot tabuláris számjeggyel szedjük, különben a
   gombsor szélessége minden lépésnél megváltozna, és a két gomb ugrálna alatta. */
.hmi-zoom {
    display: inline-flex;
    align-items: center;
    gap: 1px;
}

.hmi-zoom__btn,
.hmi-zoom__val {
    height: 26px;
    padding: 0 .4rem;
    font: inherit;
    font-size: .8rem;
    color: var(--text-clr);
    background: none;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
}

.hmi-zoom__btn {
    min-width: 26px;
    font-size: 1rem;
    line-height: 1;
}

.hmi-zoom__val {
    min-width: 3.6rem;
    font-variant-numeric: tabular-nums;
    color: var(--muted-clr);
}

.hmi-zoom__btn:hover:not(:disabled),
.hmi-zoom__val:hover {
    background: color-mix(in srgb, var(--primary-clr) 12%, transparent);
    color: var(--primary-clr);
}

.hmi-zoom__btn:focus-visible,
.hmi-zoom__val:focus-visible {
    outline: 2px solid var(--primary-clr);
    outline-offset: -2px;
}

.hmi-zoom__btn:disabled {
    color: var(--extra-muted-clr);
    cursor: default;
}

/* Az offline sáv a blokkon BELÜL egy teljes szélességű csík, nem lebegő doboz: a saját kerete és
   lekerekítése lekerül róla, marad az alsó elválasztó vonal és a figyelmeztető tónus.

   A hiányzó-kötés sáv ugyanígy viselkedik, és ugyanezért: a kettő egymás alatt is megjelenhet, tehát
   egyforma csíknak kell lennie — különben az egyik lebegő kártyaként ülne a másik alatt. */
.hmi-shell .hmi-offline,
.hmi-shell .hmi-missing {
    margin: 0;
    border-width: 0 0 1px;
    border-radius: 0;
}

/* A BLOKK TÖRZSE: bal elem-sáv | vászon | inspektor.

   ⚠️ `minmax(0, 1fr)` és nem `1fr`: egy rács-elem alapértelmezett minimális szélessége `auto`, ami a
   TARTALOMHOZ igazodna — az inspektor számmezői így kitolnák a vásznat a rácsból, ahelyett hogy az
   zsugorodna. */
.hmi-shell__body {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
}

/* TELJES elrendezés (~1400px fölött): bal sáv | vászon | inspektor.
   13rem a bal sáv, mert az utolsó szakasza a háttérkép: a miniatűr és a két gombja alatta ennél
   keskenyebben már összenyomódna. */
.hmi-shell__body--edit {
    grid-template-columns: 13rem minmax(0, 1fr) 17rem;
    /* A bal sáv és az inspektor MINDKÉT sort átfogja; a középső oszlopban felül a vászon, alatta a
       billentyű-útmutató. */
    grid-template-areas:
        "rail work side"
        "rail keys side";
    /* ⚠️ `auto 1fr`, és ez nem elhagyható. Alapértelmezett (`auto auto`) sorokkal a KÉT SORT ÁTFOGÓ
       bal sáv és inspektor többletmagasságát a böngésző SZÉTOSZTJA a két sor között — a vászon sora
       is megnő, tehát az útmutató elszakad a vászontól, és a lecsukott felirat a semmi közepén
       lebeg. Így viszont a vászon sora pontosan tartalommagasságú, a maradékot az útmutató sora
       kapja, a benne lévő <details> pedig (blokk-elem lévén) a doboza TETEJÉN kezdődik — vagyis
       közvetlenül a vászon alatt. A megnyúló doboz háttere egyben a munkafelületet is levezeti az
       oszlop aljáig. */
    grid-template-rows: auto 1fr;
}

/* A név szerinti elhelyezés CSAK a szerkesztő rácsában érvényes — nézetben a törzsnek nincsenek
   nevesített területei, és a munkaterület magától az egyetlen oszlopba kerül. */
.hmi-shell__body--edit > .hmi-shell__rail {
    grid-area: rail;
}

.hmi-shell__body--edit > .hmi-shell__work {
    grid-area: work;
}

.hmi-shell__body--edit > .hmi-shell__side {
    grid-area: side;
}

/* BAL SÁV: elem-hozzáadás + (utolsó szakaszként) a háttérkép. */
.hmi-shell__rail {
    display: flex;
    flex-direction: column;
    gap: .8rem;
    min-width: 0;
    padding: .7rem .6rem;
    border-right: 1px solid var(--c-border-subtle);
}

/* JOBB SÁV: a kijelölt elem tulajdonságai. */
.hmi-shell__side {
    display: flex;
    flex-direction: column;
    min-width: 0;
    border-left: 1px solid var(--c-border-subtle);
}

/* MUNKATERÜLET: a vászon körüli levegő. Saját, kissé eltérő felülettel — így a rajz széle akkor is
   látszik, ha a panelnek nincs háttérképe, és nagyításkor a vásznon kívüli terület is egyértelműen
   „nem a panel". Ugyanezt a felületet viszi a billentyű-útmutató sávja is (`keys` rácsterület),
   hogy széles ablakban a kettő egyetlen munkafelületnek olvasódjon. */
.hmi-shell__work {
    min-width: 0;
    padding: .75rem;
    background: var(--c-surface-elevated);
}

.hmi-work__view {
    min-width: 0;
}

/* NÉZŐKERET — ez görög, ha a nagyítás miatt a vászon nem fér el.

   A magasság-korlát nélkül a nagyítás az EGÉSZ LAPOT nyújtaná meg: a bal és a jobb sáv lecsúszna a
   képernyőről, és a szerkesztő gyakorlatilag használhatatlanná válna 200% fölött. Így viszont a
   blokk mérete állandó, és csak a rajz mozog benne.
   `vh` és nem fix pixel: egy 4K-s monitoron a fél képernyő is bőven több, mint egy laptopon.

   Szerkesztő módban MINDIG áll a korlát: ott a bal és a jobb sávnak is a képernyőn kell maradnia,
   különben a szerkesztő 200% fölött használhatatlan.

   ⚠️ NÉZŐ MÓDBAN viszont CSAK 100% FÖLÖTT (F14.3). A korlát és a belső görgetés a NAGYÍTÁSHOZ van;
   100%-on egy magas panelt továbbra is a LAP görget, ahogy eddig — feltétel nélkül kirakva
   visszakapnánk azt a fölösleges, beágyazott görgetősávot, ami miatt ez korábban szerkesztő-módra
   szűkült. A `--zoomed` osztályt a lap teszi ki, a nagyítás értékéből. */
.hmi-shell__body--edit .hmi-work__view,
.hmi-work__view--zoomed {
    max-height: min(72vh, 900px);
    overflow: auto;
}

/* A nagyítás a SZÉLESSÉGET szorozza, nem `transform: scale()`. A transform a layout-méretet nem
   változtatja meg, tehát a nézőkeret nem tudná, mekkora területet kell görgetnie — a nagyított rajz
   egyszerűen kilógna, görgetősáv nélkül. Így viszont a stage tényleg akkora lesz, a magasságot pedig
   az `aspect-ratio` adja hozzá magától.

   `margin: 0 auto`: 100% alatt középre kerül; fölötte a szabály magától hatástalan (egy a
   szülőjénél szélesebb blokk auto-margója 0-ra oldódik fel), tehát a bal széle NEM vágódik le. */
.hmi-work__view > .hmi-stage {
    width: calc(var(--hmi-zoom, 1) * 100%);
    margin: 0 auto;
}

/* ⚠️ A RESPONSZÍV FELÜLÍRÁSOK NEM ITT VANNAK, hanem a fájl VÉGÉN. Ez nem ízlés kérdése: a
   `@media` blokk nem emel fajsúlyt, tehát azonos szelektor esetén a KÉSŐBBI szabály nyer. Itt állva
   a blokk a `.hmi-rail`, a `.hmi-bgcard` és a `.hmi-railsec` (később definiált) alapszabályait NEM
   tudta felülírni — a felső sávban ezért maradtak egymás alatt a csoportok, és ezért maradt rajtuk
   a szakasz-elválasztó vonal. Lásd a lap alján a „RESPONSZÍV ELRENDEZÉS" szakaszt. */

/* A fájlválasztó a DOM-ban van, de nem látszik: a látható gombról JS kattintja meg (hmi-io.js).
   A display:none-ra tett input .click()-je is megnyitja a párbeszédablakot. */
.hmi-file-input {
    display: none;
}

/* A stage a panel KÉPARÁNYÁT tartja (inline aspect-ratio), a vászon pedig kitölti — így a panel
   felbontásfüggetlen, és a viewBox skálázása nem letterboxol. */
.hmi-stage {
    position: relative;
    width: 100%;
    background: var(--list-bg);
    border: var(--border-thin);
    border-radius: var(--list-border-radius);
    overflow: hidden;
}

/* ⚠️ Szerkesztő módban a stage-nek MÁR NINCS kék kerete. Amíg a vászon önálló, keretes kártyaként
   állt a lapon, az volt az „élesben vagy" jel; a szerkesztő-blokk bevezetése óta ezt a blokk külső
   kerete viszi, és a vászon a munkaterület süllyesztett felületén ül. Egy második kék keret a
   blokkon belül csak vibrálna. Ami itt marad, az mind VISELKEDÉS, nem díszítés: */
.hmi-stage--edit {
    /* Elemre kattintva ne induljon szövegkijelölés az SVG feliratain — húzáskor ez a böngésző
       alapértelmezett gesztusa lenne, és felülütné a sajátunkat. */
    user-select: none;
    /* ⚠️ ÉRINTÉSHEZ KÖTELEZŐ: enélkül a böngésző az első ujjmozdulatot GÖRGETÉSNEK veszi, elnyeli a
       pointermove-okat, és a húzás egyszerűen nem indul el. Ára, hogy szerkesztés közben a vászonról
       nem lehet görgetni — a nagyított nézet mozgatására viszont ott a KÉZ eszköz, ami ugyanezen a
       pointer-úton megy, tehát érintésen is működik. */
    touch-action: none;
}

/* A stage fókuszálható, mert a billentyűparancsok rajta ülnek: szerkesztéskor a Delete / Esc /
   Ctrl+A / Ctrl+Z, néző módban pedig a nagyítás (+ / − / 0).

   ⚠️ A GYŰRŰ MINDKÉT MÓDBAN KELL — korábban csak a `--edit` változaton volt. Amíg nézetben a stage
   nem is volt fókuszálható, ez nem hiányzott; az F14.3 óta viszont van mit csinálni vele, és
   jelzés nélkül a Tab-bal érkező felhasználó nem tudja megmondani, hogy a vásznon áll-e a fókusz
   (felhasználói hibajelentés nyomán).

   Egérkattintásra NINCS gyűrű — az minden elem-kijelöléskor felvillanna —, billentyűzetről viszont
   VAN. ⚠️ A sorrend számít: azonos fajsúlyú szabályok közül a KÉSŐBBI nyer. */
.hmi-stage:focus {
    outline: none;
}

.hmi-stage:focus-visible {
    outline: 2px solid var(--primary-clr);
    outline-offset: -2px;
}

/* Szerkesztéskor az elemek megfoghatók — a kurzor mondja meg, hogy nem szöveget jelölsz ki, és hogy
   az elem MOZGATHATÓ, nem csak kattintható. */
.hmi-stage--edit .hmi-el__hit {
    cursor: move;
}

/* Hover-visszajelzés: enélkül csak találgatni lehet, hol ér véget egy elem doboza — egy feliratnál
   ez különösen nem nyilvánvaló. A CSS `fill` felülüti a `fill="none"` attribútumot. */
.hmi-stage--edit .hmi-el:hover .hmi-el__hit {
    fill: var(--hmi-accent);
    fill-opacity: .07;
}

/* KÉZ ESZKÖZ: az egész vászon a NÉZŐPONTOT húzza, tehát az elemek fölött sem a mozgatás kurzora
   látszik, és nincs hover-kiemelés sem — semmit nem lehet megfogni vagy kijelölni.
   ⚠️ Mindhárom szabály azonos fajsúlyú a fenti `--edit` párjával, tehát a SORRENDÜK dönt: ezeknek
   UTÁNA kell állniuk. */
.hmi-stage--pan,
.hmi-stage--pan .hmi-el__hit {
    cursor: grab;
}

.hmi-stage--pan:active,
.hmi-stage--pan:active .hmi-el__hit {
    cursor: grabbing;
}

.hmi-stage--pan .hmi-el:hover .hmi-el__hit {
    fill: none;
}

/* HOVER-POPUP (F22) — a vászon FÖLÖTTI HTML-réteg, néző módban.

   A doboz a MUTATÓT KÖVETI: a helyét a `hmi-popup.js` számolja. Azért JS-ből, mert C#-ból minden
   egérmozdulat végigfuttatná a lap render-fáját.

   ⚠️ AZ ITTENI `left`/`top` CSAK A KIINDULÓ HELY. Amint a doboz létezik, a modul BEÁGYAZOTT STÍLUST
   ír rá, ami ezt felülírja — azért nem a stage `--hmi-px` / `--hmi-py` változóit írja folyamatosan,
   mert az egyéni tulajdonság ÖRÖKLŐDIK, tehát minden mozdulatnál az egész SVG-részfa stílusát
   újraszámoltatná. A `--hmi-*` így csak addig él, amíg az első hover meg nem történik.

   A tartalék érték (a stage bal felső sarka) ezen felül is csak akkor látszana, ha a modul egyáltalán
   nem töltődne be. Korábban ez volt a normál útvonal, és ebből lett a „minden popup a bal felső
   sarokban jelenik meg" hiba — lásd hmi-popup.js.

   ⚠️ `pointer-events: none` — KÖTELEZŐ, nem finomítás. A doboz a kurzor mellett áll; ha fogná a
   mutatót, ráérve az elem `mouseleave`-je tüzelne, a popup eltűnne, a mutató visszakerülne az
   elemre, és VÉGTELEN CIKLUSBAN villogna. Cserébe a tartalma nem jelölhető ki — az ára megéri, és
   a követés miatt nincs is miért ráállni. */
.hmi-pop {
    position: absolute;
    left: var(--hmi-px, .5rem);
    top: var(--hmi-py, .5rem);
    z-index: 3;

    /* Két oszlop: keskeny felirat + tartalom. A `max-content` nem nyúlik el egy hosszú eszköznévtől,
       a második oszlop viszont tördelhet. */
    display: grid;
    grid-template-columns: auto;
    gap: 0;
    min-width: 9rem;
    max-width: min(16rem, 80%);
    padding: .6rem .7rem;
    border-radius: 10px;
    background: var(--c-surface-elevated);
    border: 1px solid var(--c-border-subtle);
    box-shadow: 0 8px 24px rgba(0, 0, 0, .28);
    font-size: .8rem;
    line-height: 1.35;
    color: var(--text-clr);
    pointer-events: none;

    /* HALVÁNYÍTÁS MINDKÉT IRÁNYBAN. A doboz mindig a DOM-ban van, csak a láthatósága kapcsol
       (`--on`) — egy hoverenként létrehozott és eldobott elemen a kifelé tartó átmenet nem futna le,
       tehát a popup mindig ugrásszerűen tűnne el.

       160 ms, apró emelkedéssel és egy hajszálnyi nagyítással. Rövidebbre véve (120 ms, csak
       átlátszóság) a mozgó kurzor mellett gyakorlatilag észrevehetetlen volt — egy követő buboréknál
       a szem a HELYVÁLTOZÁST követi, tehát az átmenetnek a méretben és a fedettségben együtt kell
       látszania, hogy egyáltalán regisztráljuk.

       ⚠️ TRANSITION, nem animation — és ez itt kivételesen számít: az `app.css`
       `prefers-reduced-motion` blokkja a `transition-duration`-t nullázza, tehát ez a halványítás
       magától kikapcsol mozgásérzékenységre állított gépen. (Egy `animation`-höz külön
       `animation: none` kellene — az a HMI-ben visszatérő csapda.)
       📌 Következmény, amit érdemes tudni hibakeresésnél: a rendszerszintű „animációk kikapcsolása"
       (Windows: Kisegítő lehetőségek → Vizuális effektusok) ezt a halványítást is elnyeli — nem
       hiba, hanem a kért viselkedés.

       A `visibility` azért kell a sorba, hogy a rejtett doboz ne csak átlátszó, hanem a
       kereső/gyorsítás szempontjából is kivett legyen; a késleltetése miatt a kihalványulás VÉGÉN
       vált, nem az elején. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(4px) scale(.97);
    transition: opacity .16s ease-out, transform .16s ease-out, visibility 0s linear .16s;
}

.hmi-pop--on {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: opacity .16s ease-out, transform .16s ease-out, visibility 0s;
}

/* A LEGELSŐ popup is halványodjon. A doboz csak az első hoverkor kerül a DOM-ba, márpedig egy frissen
   beszúrt elemen a böngésző nem futtat átmenetet — az első buborék tehát azonnal ott lenne, a többi
   meg halványodna. Ez a szabály adja meg neki a kiinduló állapotot.

   Ráadás, nem feltétel: ahol a `@starting-style` nem ismert, ott pontosan a korábbi viselkedés marad
   (az első megjelenés ugrik, a többi halványodik) — nincs mit tartalékolni. */
@starting-style {
    .hmi-pop--on {
        opacity: 0;
        transform: translateY(4px) scale(.97);
    }
}

/* A fejléc és a mező-feliratok ugyanaz a tipográfia: apró, ritkított, csupa nagybetű, halvány. Ez
   viszi az egész doboz olvasási ritmusát — a felirat SOSEM verseng a tartalommal. */
.hmi-pop__head,
.hmi-pop__lbl {
    font-size: .68rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--extra-muted-clr, var(--muted-clr));
}

/* A feliratok fölött hézag, a hozzájuk tartozó érték alatt nem — így a pár összetartozik, és a
   blokkok elválnak. Az elsőnek nem kell (a nagy érték már elválasztja). */
.hmi-pop__lbl {
    margin-top: .5rem;
}

.hmi-pop__val {
    margin-top: .15rem;
    font-size: 1.35rem;
    font-weight: 600;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
}

.hmi-pop__row {
    font-weight: 500;
    overflow-wrap: anywhere;
}

/* A kort a komponens színezi (a szerep-rétegből), az időpont marad halvány: az azonosít, nem ítél. */
.hmi-pop__at {
    margin-left: .4rem;
    color: var(--muted-clr);
    font-weight: 400;
    font-variant-numeric: tabular-nums;
}

/* A kép-elem miniatűrje az inspektorban. Ugyanaz a szerep, mint a háttérkép miniatűrjének: a vásznon
   az elem lehet apró, elforgatott vagy takarásban — itt látszik biztosan, mi van rajta.
   A magasság-korlát nem esztétika: egy álló fotó különben letolná a lap aljára az alatta lévő
   gombokat, épp azokat, amikkel cserélni lehetne. */
.hmi-insp__thumb {
    display: block;
    width: 100%;
    max-height: 7rem;
    object-fit: contain;
    border-radius: 6px;
    background: var(--c-surface-sunken, var(--c-surface-elevated));
    border: 1px solid var(--c-border-subtle);
}

/* ÁLLAPOT-JELEK (F12.3) — az elem-burok rajzolja, mindkét módban.

   KÉT CSATORNA: a szaggatott keret azt mondja, hogy a PANEL hibás (elárvult vagy be nem kötött
   elem — magától nem jön rendbe), a bal felső sarok-jel azt, hogy az ADAT hiányzik vagy régi.
   Elárvult elemen mindkettő ott van, mert tényleg mindkettő igaz rá.

   ⚠️ A színt SZÁNDÉKOSAN nem itt adjuk meg, hanem SVG-attribútumban a HmiRoles-ból: a szerep-réteg
   témánként más értéket ad (light / dark / night), és ugyanaz a `stale` szerep festi az elem
   tartalmát is — így a jel és a szám nem tud eltérő szürkét mutatni.

   vector-effect: a vonalvastagság a KÉPERNYŐ pixeleiben értendő, nem a vászon design-egységeiben.
   Egy 1600 egység széles panel ~900 px-en jelenik meg: ott egy 2 egységes vonal 1,1 CSS-pixelre
   zsugorodna, a szaggatás pedig gyakorlatilag eltűnne. Ugyanaz a megfontolás, mint a kijelölés-
   keretnél és a rácsnál. */
.hmi-el__flag {
    stroke-width: 2px;
    stroke-dasharray: 7 5;
    vector-effect: non-scaling-stroke;
    opacity: .85;
}

.hmi-el__badge {
    stroke-width: 1.5px;
    vector-effect: non-scaling-stroke;
}

/* ── KAPCSOLHATÓ RELÉ NÉZŐ MÓDBAN (F19) ─────────────────────────────────────────────────────────

   ⚠️ A szabályok a KAPCSOLÓ-ELEMEN vannak, nem a burkon (`.hmi-el`): a találati terület pontosan a
   sín, nem a kártya. A kártya nagyobbik fele tábla és felirat, és egy feliratra kattintva kapcsolni
   meglepetés — egy operátori panelen pedig a meglepetés drága. A burokra kötve ráadásul az EGÉSZ
   doboz „kapcsolhatónak" látszana.

   A kurzor az egyetlen visszajelzés, ami MÉG A KATTINTÁS ELŐTT megmondja, reagálni fog-e. Az OKOT
   nem ez mondja meg (arra ott az offline sáv, a sarok-jel és a popup) — csak azt, hogy hiába
   próbálod. HÁROM külön üzenet: „kapcsolható" · „várj" · „hiába próbálod"; egy kurzorra fogva az
   utolsó kettő egyaránt elromlottnak látszana. */
.hmi-switch__hit--on {
    cursor: pointer;
}

.hmi-switch__hit--busy {
    cursor: progress;
}

.hmi-switch__hit--off {
    cursor: not-allowed;
}

/* ⚠️ A fókusz-gyűrű a MŰKÖDŐ kapcsolón kötelező: ez az első HMI-elem, amit billentyűzetről is
   működtetni lehet, és fókusz-jelzés nélkül a felhasználó nem tudná, MIT fog kapcsolni az Enter.
   `:focus-visible`, nem `:focus` — egérkattintásnál a gyűrű minden kapcsolásnál felvillanna.

   A gyűrűt maga a találati téglalap viseli, tehát pontosan a sín PIRULA-alakját veszi fel; a
   böngésző natív `outline`-ja ehelyett a befoglaló derékszöget rajzolná köré. A
   `non-scaling-stroke` ugyanazért kell, amiért a fogantyúknál: user-egységben a vonal a panel
   felbontásától függően lenne hajszálvékony vagy ormótlan. */
.hmi-switch__hit:focus {
    outline: none;
}

.hmi-switch__hit:focus-visible {
    stroke: var(--hmi-accent);
    stroke-width: 2px;
    vector-effect: non-scaling-stroke;
}

/* ── PULZÁLÁS TÜZELŐ RIASZTÁSNÁL (F14.1) ────────────────────────────────────────────────────────

   Elemenként kapcsolható (`props.pulse`), és CSAK amíg tényleg tüzel a riasztás — a feltételt a
   C# adja (HmiValue.IsAlarming), itt már csak a mozgás van.

   ⚠️ NEM tűnik el, csak halványul (.45). Egy villogó readout, amit az idő felében nem lehet
   elolvasni, rosszabb, mint egy mozdulatlan: a pulzálás azt mondja, hogy IDE NÉZZ, nem azt, hogy
   „olvasd el, ha elkapod". Ugyanezért `opacity` és nem szín-váltás: a riasztás színének végig
   látszania kell.

   ⚠️ ~0,9 Hz. A 3 Hz fölötti villogás fotoszenzitív rohamot válthat ki (WCAG 2.3.1) — ez bőven
   alatta van, és egyben az operátori panelek szokásos riasztás-üteme. */
@keyframes hmi-pulse {
    50% { opacity: .45; }
}

.hmi-el--pulse {
    animation: hmi-pulse 1.1s ease-in-out infinite;
}

/* ⚠️ KÜLÖN `animation: none` KELL. Az app.css reduced-motion blokkja CSAK a `transition-duration`-t
   nullázza, tehát az animáció átmenne rajta — épp azon a gépen, ahol a felhasználó kifejezetten
   kérte, hogy ne mozogjon semmi. A színt és a többi jelzést ez nem érinti: a riasztás ugyanúgy
   látszik, csak nem mozog. */
@media (prefers-reduced-motion: reduce) {
    .hmi-el--pulse {
        animation: none;
    }
}

/* Az ikonbetűs jel (elavult → óra). A .hmi-canvas `font-family: inherit`-je nem elég: ikon-glifa más
   betűcsaláddal üres négyzet lenne. Ugyanaz a beállítás, mint a kapcsoló-elem ikonjánál. */
.hmi-el__ico {
    font-family: 'Material Symbols Outlined';
    font-weight: normal;
    font-style: normal;
    letter-spacing: normal;
}

/* Az írásjeles jel („?" / „–"). Félkövér, mert egy vékony kérdőjel egy tarka fotó fölött, korong
   méretben, alig látszik. */
.hmi-el__mark {
    font-weight: 700;
}

/* KIJELÖLÉS — a HmiCanvas külön rétege, az összes elem UTÁN rajzolva.

   CSS-ből festve, nem SVG prezentációs attribútumokból: a hover-visszajelzés bizonyítja, hogy ez az
   út ebben a kontextusban működik.

   vector-effect: a keret a KÉPERNYŐ pixeleiben legyen 2 px vastag, ne a vászon design-egységeiben —
   egy 1600 egység széles panel ~900 px-en jelenik meg, ott a 2 egységes vonal 1,1 CSS-pixelre
   zsugorodna, a szaggatás pedig gyakorlatilag eltűnne. */
.hmi-sel rect {
    fill: var(--hmi-accent);
    fill-opacity: .18;
    stroke: var(--hmi-accent);
    stroke-width: 2px;
    stroke-dasharray: 6 4;
    vector-effect: non-scaling-stroke;
}

/* ÁTMÉRETEZŐ FOGANTYÚK — a kijelölt elem négy sarkán, csak egyetlen kijelölésnél.
   Fogantyúnként KÉT kör: a látvány és a találat mérete másképp adódik (lásd HmiCanvas).

   TALÁLATI KORONG: valódi sugár design-egységben, `pointer-events: all` — az a festetlen felületen is
   fog, tehát a teljes korong fogható, nem csak egy körvonal.
   ⚠️ Azért nem a látható pont körvonalára bízzuk, mert a böngészők a `non-scaling-stroke`-os
   alakzatot a NEM skálázott geometria szerint találgatják: a fogási felület így sokkal kisebb lett
   volna, mint a képernyőn látott pont. */
.hmi-handles__hit {
    fill: none;
    pointer-events: all;
}

/* LÁTHATÓ PONT: a VONALVASTAGSÁGBÓL van, nem a sugárból (r ≈ 0). A `non-scaling-stroke` mellett a
   stroke-width KÉPERNYŐ-képpontban értendő, tehát a pont minden panelméretnél és ablakszélességnél
   ugyanakkora. Nem fog kattintást — azt a korong intézi. */
.hmi-handles__dot {
    fill: none;
    stroke: var(--hmi-accent);
    stroke-width: 13px;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

/* FORGATÓ FOGANTYÚ. A kar köti az elemhez — enélkül egy magában lebegő pont lenne a doboz fölött,
   amiről semmi nem mondja meg, hogy az elemhez tartozik. `non-scaling-stroke`, mint a kijelölés-
   kereten: a vonal a képernyőn legyen hajszálvékony, ne a vászon egységeiben. */
.hmi-handles__arm {
    stroke: var(--hmi-accent);
    stroke-width: 1px;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.hmi-handles__rot {
    cursor: grab;
}

.hmi-handles__rot:active {
    cursor: grabbing;
}

/* ════════════════════════ VEZETÉK ════════════════════════

   A színt és a vastagságot az SVG-attribútumok viszik (a szerep-rétegből, mint mindenhol) — ide csak
   az kerül, ami CSS-kérdés: az áramlás animációja és a kijelölés glóriája. */

/* EGY SZÍN, KÉT SZEREPBEN. A `--hmi-wire-c` a választott szerep színe, a csoportra írva; a NYÍLHEGY
   ezt kapja teljes erővel, a CSŐ pedig ugyanezt a felület felé keverve. Így a cső árnyalata magától
   követi a témát (light módban világos, dark/night módban sötét felé halványul), és nem lehet olyan
   párosítás, ahol a kettő nem tartozik össze — egyetlen forrásuk van. */
.hmi-wire__line {
    stroke: color-mix(in srgb, var(--hmi-wire-c, var(--hmi-muted)) 40%, var(--hmi-surface));
}

.hmi-wire__chev {
    stroke: var(--hmi-wire-c, var(--hmi-muted));
}

/* ── ÁRAMLÁS: nyílhegyek utaznak a csőben ─────────────────────────────────────────────────────

   ⚠️ EZ VÁLTOTTA LE A SZAGGATOTT VONALAT (`stroke-dasharray` + csúszó `stroke-dashoffset`), mert az
   a TÖRÉSPONTOKNÁL VILLOGOTT. Egy szaggatott vonal minden darabkája külön körvonal; amikor egy dash
   épp áthaladt a derékszögön, a sarok illesztése képkockánként megjelent és eltűnt. Ez a szaggatás
   és az éles sarok találkozásának ismert műterméke — nem lehetett volna „finomhangolni".

   Itt minden nyílhegy ÖNÁLLÓ alakzat, ami a nyomvonalon utazik: nincs körvonal-darabolás, tehát
   nincs mit villogtatni. Ráadás, hogy az `offset-rotate: auto` miatt a nyíl BEFORDUL a kanyarban —
   pontosan az az információ, amiért az animáció egyáltalán van.

   A pályát (`--hmi-path`) és az időtartamot (`--hmi-flow-dur`) a CSOPORT hordozza, örökléssel; a
   nyílhegyek csak a saját `animation-delay`-üket kapják, és az osztja el őket egyenletesen. */
.hmi-wire__chev {
    offset-path: var(--hmi-path);
    offset-rotate: auto;
    animation: hmi-wire-flow var(--hmi-flow-dur, 3s) linear infinite;
}

/* Visszafelé: az animáció fordítva fut, a nyíl pedig 180°-kal fordul — enélkül hátrafelé haladna,
   miközben előre mutat. */
.hmi-wire__flow--rev .hmi-wire__chev {
    offset-rotate: auto 180deg;
    animation-direction: reverse;
}

@keyframes hmi-wire-flow {
    from { offset-distance: 0%; }
    to   { offset-distance: 100%; }
}

/* ⚠️ ANIMATION, nem transition — tehát az `app.css` `prefers-reduced-motion` blokkja NEM fogja meg
   (az csak a `transition-duration`-t nullázza). A külön kikapcsolás KÖTELEZŐ; ez a HMI-ben
   visszatérő csapda, ugyanaz, ami a pulzálásnál is.

   📌 De itt NEM `animation: none`, hanem MEGÁLLÍTÁS. A nyílhegyek helyét a negatív késleltetés adja;
   megállítva mindegyik ott marad, ahol épp tart, tehát a csövön végig ott sorakoznak a nyilak, a
   helyes irányba fordulva. Az `animation: none` ezzel szemben mindet a pálya elejére, egymásra
   tolná. Így a MOZGÁS tűnik el, az INFORMÁCIÓ (merre folyik) megmarad — ami pontosan az, amit a
   mozgáscsökkentés kér. */
@media (prefers-reduced-motion: reduce) {
    .hmi-wire__chev {
        animation-play-state: paused;
    }
}

/* Ahol a motion path nem ismert, ott a nyílhegyek egymásra torlódnának a doboz origójában — inkább
   ne legyenek. A cső és a színe marad, csak az áramlás nem látszik. */
@supports not (offset-path: path("M 0 0")) {
    .hmi-wire__flow {
        display: none;
    }
}

/* A KIJELÖLÉS a vezetéken a NYOMVONALAT követi, nem a dobozát: annak a doboza a két végpont közti
   teljes terület, tehát egy átlós csőnél fél vászonnyi halvány téglalap lenne, ami mindent eltakar,
   amiről épp nem beszél. A glória a vonal alatt fut (a rajzolási sorrendben a burok UTÁN jön, de
   halvány, tehát nem nyomja el a csövet). */
.hmi-sel__wire {
    stroke: var(--hmi-accent);
    stroke-opacity: .45;
}

/* A CSOPORT közös doboza (kettő vagy több kijelölt elem). Ehhez képest méretez a húzás, tehát látszania
   kell — enélkül a négy fogantyú a semmiben lógna, egymástól távol eső elemek sarkainál.

   KITÖLTÉS NÉLKÜL, és ez nem stílus-kérdés: a doboz a tagjai közötti ÜRES területet is lefedi, egy
   fátyol tehát pont azt takarná ki, ami alatta van. Vékonyabb és sűrűbb szaggatás, mint az elemek
   kijelölés-keretéé (`.hmi-sel`): az egy kijelölt DOLOG, ez egy művelet kerete. */
.hmi-handles__box {
    fill: none;
    stroke: var(--hmi-accent);
    stroke-width: 1px;
    stroke-dasharray: 3 3;
    vector-effect: non-scaling-stroke;
    pointer-events: none;
}

.hmi-handles__h--nw,
.hmi-handles__h--se {
    cursor: nwse-resize;
}

.hmi-handles__h--ne,
.hmi-handles__h--sw {
    cursor: nesw-resize;
}

/* KERETES KIJELÖLÉS. Ugyanaz a szaggatás, mint a kijelölés-kereté, de kitöltés nélkül: ez nem egy
   kijelölt dolog, hanem egy művelet folyamatban. */
.hmi-marquee {
    fill: var(--hmi-accent);
    fill-opacity: .08;
    stroke: var(--hmi-accent);
    stroke-width: 1px;
    stroke-dasharray: 4 3;
    vector-effect: non-scaling-stroke;
}

/* ----------------------------------------------------------------------------
   KÖTÉSVÁLASZTÓ DIALÓGUS (HmiBindingPickerDialog)
   A keresőt, a fülsávot és az üres állapotot a CardPickerDialog `.picker-*` osztályai adják — a két
   választó így ránézésre ugyanaz a felület. Itt CSAK a lista van, mert az más: nem kártya-rács,
   hanem sűrű, egysoros lista élő értékkel.
   -------------------------------------------------------------------------- */
.hmi-pick__list {
    display: flex;
    flex-direction: column;
    gap: 1px;
    /* A dialógus fix magasságú; a lista görögjön, ne az egész body — így a kereső és a fülsáv
       a helyén marad görgetés közben is. */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* Eszköz-fejléc. A metrika-nevek önmagukban nem egyediek („Fesz." minden eszközön ott van), enélkül
   a lista olvashatatlan. Ragadós, hogy hosszú listában is látszódjon, melyik eszköznél tartasz. */
.hmi-pick__group {
    position: sticky;
    top: 0;
    z-index: 1;
    padding: .45rem .55rem .2rem;
    font-size: .7rem;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--muted-clr);
    background: var(--dialog-bg, var(--list-bg));
}

.hmi-pick__row {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    width: 100%;
    padding: .45rem .55rem;
    text-align: left;
    font-size: .9rem;
    color: var(--text-clr);
    background: none;
    border: none;
    border-radius: var(--list-border-radius);
    cursor: pointer;
}

.hmi-pick__row:hover {
    background: color-mix(in srgb, var(--primary-clr) 10%, transparent);
}

/* A JELENLEGI kötés. A keret + a háttértónus az EGYETLEN jelölés — szándékosan nincs pipa: az csak a
   kiválasztott soron jelenne meg, tehát azon az egy soron elcsúsztatná az érték- és állapot-oszlopot
   a többihez képest. A keret perifériás látással is megmondja, hol áll a kijelölés, és nem mozdít
   el semmit. */
.hmi-pick__row--on {
    background: color-mix(in srgb, var(--primary-clr) 14%, transparent);
    box-shadow: inset 0 0 0 1px var(--primary-clr);
}

.hmi-pick__row--none {
    margin-bottom: .25rem;
}

.hmi-pick__name {
    flex: 1 1 auto;
    min-width: 0;
}

/* Az érték tabuláris számjegyekkel, mint a vásznon: enélkül a jobbra igazított oszlop
   soronként arrébb ugrana. */
.hmi-pick__val {
    color: var(--muted-clr);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.hmi-pick__state {
    font-size: .78rem;
    font-weight: 600;
    white-space: nowrap;
}

/* OFFLINE SÁV — egy sor, a vászon fölött. Nem modális és nem takar: az utoljára ismert állapot attól
   még a legjobb, amink van.

   A `warn` szerep, nem a `stale`: a sáv azt mondja, hogy FIGYELJ (megszakadt a kapcsolat), az
   értékek pedig külön, a saját stale színükkel azt, hogy NE HIGGY nekik. Két külön üzenet, két külön
   csatorna — egy szürke sáv beleolvadna abba, amiről beszél. */
.hmi-offline {
    display: flex;
    align-items: center;
    gap: .45rem;
    margin-bottom: .6rem;
    padding: .4rem .6rem;
    font-size: .85rem;
    color: var(--hmi-warn);
    border: var(--border-thin);
    border-color: color-mix(in srgb, var(--hmi-warn) 45%, transparent);
    border-radius: var(--list-border-radius);
    background: color-mix(in srgb, var(--hmi-warn) 10%, transparent);
}

/* HIÁNYZÓ KÖTÉSEK SÁVJA — „N kötés nem található ezen a hajón". Ugyanaz az alak, mint az offline sávé
   (a kettő megjelenhet egymás alatt is), de `alarm` szerepben:

   • az offline sáv ÁTMENETI állapotról szól („most nincs kapcsolat, várj"), ezért figyelmeztetés;
   • ez viszont a panel TARTALMÁRÓL („ez a kötés ezen a hajón nem létezik"), ami magától nem javul meg —
     addig ott lesz a kérdőjel a vásznon, amíg valaki újra nem köti. Ez hiba, nem várakozás.

   A szerep-token (nem konkrét szín) itt is kötelező: night témában a --c-primary bíborvörös és a
   --c-text-primary is a hibaszínnel egyezik, tehát egy kézzel írt vörös beleolvadna a szövegbe. A
   --hmi-* réteg témánként FÉNYERŐBEN választja szét a szerepeket.

   A `color` itt — az offline sávhoz hasonlóan — az IKONT festi (az currentColor-ral rajzol): a <span>
   szövegére az app.css közvetlen `span { color: ... }` szabálya érvényes, tehát az a lap
   szövegszínével jelenik meg. Ez szándékosan marad így: a tónusos keret + színes ikon adja a
   jelentést, a mondat maga pedig teljes kontraszttal olvasható — a két sáv így egyforma is. */
.hmi-missing {
    display: flex;
    align-items: center;
    gap: .45rem;
    margin-bottom: .6rem;
    padding: .4rem .6rem;
    font-size: .85rem;
    color: var(--hmi-alarm);
    border: var(--border-thin);
    border-color: color-mix(in srgb, var(--hmi-alarm) 45%, transparent);
    border-radius: var(--list-border-radius);
    background: color-mix(in srgb, var(--hmi-alarm) 10%, transparent);
}

/* Abszolút, nem folyó elem: a magasságát így közvetlenül a stage képarányos doboza adja, és nem függ
   attól, hogy a böngésző hogyan oldja fel a %-os magasságot egy aspect-ratio szülőn. */
.hmi-canvas {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    /* A feliratok a lap betűcsaládját örököljék, ne az SVG alapértelmezett szerifes fontját. */
    font-family: inherit;
}

/* A GÖRBE skála-feliratai (F24). Csak a számjegy-igazítás miatt van saját szabályuk: a minimum és a
   maximum egymás alatt áll, és arányos számjegyekkel a tizedesvessző soronként máshova esne — ami
   pont azt a leolvasást nehezíti, amiért a skála egyáltalán ott van.
   ⚠️ A SZÍNT és a MÉRETET nem itt adjuk: azok az elem geometriájából és a szerep-rétegből
   származnak, tehát attribútumként mennek ki (lásd HmiChartEl). */
.hmi-chart__axis {
    font-variant-numeric: tabular-nums;
}

/* ÉLŐ MINIATŰR (F16): ugyanez a vászon a listakártyán, kicsiben.

   ⚠️ EZ A SZABÁLY A `.hmi-canvas` UTÁN ÁLL, és ott is kell maradnia. Mindkettő egyosztályos, tehát
   AZONOS FAJSÚLYÚ — a későbbi nyer. Előtte állva (a kártya-szakaszba írva) semmit nem tudna
   felülírni. Ugyanaz a csapda, ami a responszív szakaszt is a fájl végére kényszerítette.

   ⚠️ A pozicionálást NEM írjuk felül: a vászon itt is `absolute; inset: 0` marad, és a magasságát a
   kártya képarányos miniatűr-doboza adja — pontosan úgy, ahogy a panel-oldalon a stage-é. Ehhez
   viszont a doboznak POZICIONÁLTNAK kell lennie (lásd `.hmi-card__thumb`), különben a vászon
   kiszökik a lap méretébe.

   ⚠️ `pointer-events: none` — a miniatűr KÉP, nem felület. A kattintás így a kártya megnyitó
   gombjához jut. Ez viszont csak a MÁSODIK védelmi réteg: a kapcsoló-elem találati node-ja
   előnézetben ki sem kerül a DOM-ba, mert egy fókuszálható vezérlőt a billentyűzet innen is
   működtetni tudna. */
.hmi-canvas--preview {
    pointer-events: none;
}

/* A KONTAKTUS-BEMENET ikonja a vásznon. Ugyanaz a Material Symbols betűkészlet, amit a lap többi
   ikonja is használ (icons.css), és a glifa ugyanabból a közös térképből jön (IconGlyphs), amiből a
   dashboard kapcsoló-kártyájáé — a két felület jele így nem tud elcsúszni.

   ⚠️ A betűcsalád ITT kell, nem elég a `.hmi-canvas { font-family: inherit }`: az a lap
   szövegcsaládját hozza le a vászonra, egy ikon-glifa viszont csak a saját betűkészletében létezik —
   más családdal üres négyzet lenne. */
.hmi-switch__ico {
    font-family: 'Material Symbols Outlined';
    font-weight: normal;
    font-style: normal;
    letter-spacing: normal;
}

/* ÉLŐ ÉRTÉK. A tabuláris számjegyek nem kozmetika: enélkül minden frissítésnél MÁS SZÉLES lenne a
   szám (a „1" keskenyebb, mint a „8"), tehát egy középre igazított readout 20 másodpercenként
   arrébb ugrana. Egy operátori panelen a mozgás figyelemelvonás. */
.hmi-value {
    font-variant-numeric: tabular-nums;
}

/* ZÁROLT ELEM a szerkesztőben. A mutató KERESZTÜLESIK rajta: sem ő, sem a benne lévő bármi nem fog
   kattintást, tehát fölötte is lehet keretes kijelölést húzni — pontosan ezért van a zár. A rajz
   VÁLTOZATLAN (nincs halványítás, nincs jelölés): a zár a szerkesztés kényelméről szól, nem az
   elem állapotáról, és egy operátori panelen minden extra jel elvon valamitől, ami tényleg számít.
   ⚠️ Csak szerkesztő módban kerül ki az osztály — lásd HmiElement.Classes(). */
.hmi-el--locked {
    pointer-events: none;
}

/* Rátét az üres panelen — ezért abszolút, és ezért nem fog kattintást.

   ⚠️ A `pointer-events: none` KÖTELEZŐ: a doboz a teljes vászon fölé terül (inset: 0), tehát enélkül
   elnyelné a kijelölés-törlő és keretes-kijelölő kattintásokat — szerkesztő módban a vászon
   használhatatlanná válna. Ma már úgysincs benne interaktív elem: a rátét CSAK FELIRAT (a korábbi
   „Kezdés sablonból" / „Szerkesztés" gombpár lekerült róla, lásd HmiPanelPage). */
.hmi-stage__hint {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    /* `align-content` is: több sor (felirat + teendő) esetén a `place-items` csak a soron BELÜL
       igazítana, a sorok blokkja pedig felülre csúszna. */
    align-content: center;
    gap: .5rem;
    padding: 1rem;
    text-align: center;
    color: var(--muted-clr);
    font-size: .9rem;
    pointer-events: none;
}

/* Az üres panel felirata. Külön elem, mert szerkesztéskor egy MÁSIK hangsúlyú sor jön alá (a teendő). */
.hmi-hint__title {
    color: var(--muted-clr);
}

/* A szerkesztő módbeli teendő-sor. Dőlt és kisebb, mint a felirat — az állítás és a teendő így egy
   pillantással szétválik (ugyanaz a ladder, mint az inspektor üres állapotában). */
.hmi-hint__sub {
    font-size: .8rem;
    font-style: italic;
    color: var(--muted-clr);
}

/* ----------------------------------------------------------------------------
   BAL ELEM-SÁV (szerkesztő mód)
   -------------------------------------------------------------------------- */
.hmi-rail {
    display: flex;
    flex-direction: column;
    gap: .9rem;
    min-width: 0;
}

.hmi-rail__grp {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

/* Egy tétel: glifa + felirat, teljes szélességben, balra igazítva. Nem gombrács, hanem LISTA —
   a nevek különböző hosszúak, és egy rácsban a rövidek körül holt hely maradna. */
.hmi-rail__it {
    display: flex;
    align-items: center;
    gap: .5rem;
    width: 100%;
    padding: .32rem .4rem;
    font: inherit;
    font-size: .82rem;
    text-align: left;
    color: var(--text-clr);
    background: none;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
}

.hmi-rail__it:hover {
    background: color-mix(in srgb, var(--primary-clr) 12%, transparent);
    color: var(--primary-clr);
}

.hmi-rail__it:focus-visible {
    outline: 2px solid var(--primary-clr);
    outline-offset: -2px;
}

/* ⚠️ A TILTÁS EDDIG NEM LÁTSZOTT, és ez valódi hiba volt: a vezeték-tétel `disabled` attribútumot
   kapott (két kijelölt elem nélkül nincs mit összekötni), de a sávnak nem volt `:disabled` szabálya
   — tehát pontosan úgy nézett ki, mint egy működő gomb, hoverre ki is világosodott, kattintásra
   viszont nem történt semmi. A hover-szabályokat KÜLÖN kell semlegesíteni: az `opacity` alatt a
   kiemelés továbbra is átütne, és a glifa is átszíneződne. A `:disabled:hover` párosok eggyel
   nagyobb fajsúlyúak a sima `:hover`-nél, tehát a fájlbeli sorrendtől függetlenül nyernek. */
.hmi-rail__it:disabled {
    opacity: .45;
    cursor: not-allowed;
}

.hmi-rail__it:disabled:hover {
    color: var(--text-clr);
    background: none;
}

.hmi-rail__it:disabled:hover .hmi-rail__ico {
    stroke: var(--muted-clr);
}

.hmi-rail__it:disabled:hover .hmi-glyph__f {
    fill: var(--muted-clr);
}

.hmi-rail__txt {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ⚠️ A glifák festése ITT van, nem `currentColor`-ral. Az app.css a div/span/p elemekre színt
   erőltet, tehát egy span-be csomagolt ikon öröklése nem megbízható — az <svg> és a gyerekei viszont
   nem esnek abba a szabályba, így az explicit stroke/fill mindig érvényesül. */
.hmi-rail__ico {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    fill: none;
    stroke: var(--muted-clr);
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* A glifák tömör részei (lámpa-mag, nyíl, csúszka): kitöltve, körvonal nélkül.

   ⚠️ AZ OSZTÁLY NEVE `hmi-glyph__f`, NEM `hmi-rail__f`: a rajzot egy önálló komponens adja
   (HmiKindGlyph), tehát a BELSŐ osztálynevei nem tartozhatnak a befogadó felülethez. A körvonal
   színét a KÜLSŐ osztály adja (ma: .hmi-rail__ico), a tömör részekét ez — így a glifa bárhol a saját
   környezete szerint színeződik, anélkül hogy a rajzhoz hozzá kellene nyúlni. */
.hmi-glyph__f {
    fill: var(--muted-clr);
    stroke: none;
}

.hmi-rail__it:hover .hmi-rail__ico {
    stroke: var(--primary-clr);
}

.hmi-rail__it:hover .hmi-glyph__f {
    fill: var(--primary-clr);
    stroke: none;
}

/* ----------------------------------------------------------------------------
   KÖZÖS TULAJDONSÁGOK — az inspektor többes kijelölés állapotában, az igazítás alatt.
   ---------------------------------------------------------------------------- */

.hmi-multi {
    display: flex;
    flex-direction: column;
    gap: .45rem;
    padding-top: .7rem;
    margin-top: .2rem;
    border-top: 1px solid var(--c-border-subtle);
}

/* Címke + gombpár. A hosszú fogalomnév („Kártya háttér") EGYSZER áll a sor elején, nem mindkét
   gombon: így a gombok rövidek maradnak, és a név ugyanaz, mint az egyelemes kapcsolóé. */
.hmi-multi__pair {
    display: flex;
    flex-direction: column;
    gap: .25rem;
}

/* Két egyenlő gomb egy sorban. `min-width: 0` a flex-elemeken: enélkül a hosszabb felirat (és a
   mellé írt darabszám) kifeszítené a sort az oldalsávból. */
.hmi-multi__row {
    display: flex;
    gap: .35rem;
}

.hmi-multi__row > * {
    flex: 1 1 0;
    min-width: 0;
}

/* Teljes szélességű AppButton az oldalsávokban (elem törlése, háttérkép választása).
   Az AppButton alapból a tartalmához igazodik; itt a sávot akarjuk kitölteni, hogy a gombok széle
   egy vonalban legyen a mezőkkel. */
.hmi-fullbtn {
    width: 100%;
    min-width: 0;
}

/* ----------------------------------------------------------------------------
   HÁTTÉRKÉP-SZAKASZ — a BAL sáv utolsó menüpontja.

   Nem önálló kártya: a szerkesztő-blokkon belül szakasz, amit felül hajszálvonal választ el a
   hozzáadó tételektől. Azért került a bal oldalra, mert az is „letevés", csak a legalsó rétegé — a
   jobb oszlop így tisztán a kijelölt elem tulajdonságaié marad.
   -------------------------------------------------------------------------- */
/* A `.hmi-railsec` ugyanez általánosan: a bal sáv minden olyan szakasza, amit felül hajszálvonal
   választ el az előtte lévőtől (ma a háttérkép és a panel-beállítások). Egy szabály, két név — a
   `.hmi-bgcard__*` gyerekek így megtartják a beszédes előtagjukat. */
.hmi-bgcard,
.hmi-railsec {
    display: flex;
    flex-direction: column;
    gap: .45rem;
    min-width: 0;
    padding-top: .7rem;
    border-top: 1px solid var(--c-border-subtle);
}

/* ⚠️ A szakaszcím margója itt NULLA, mert a konténer `gap`-je már ad térközt. Enélkül a kettő
   ÖSSZEADÓDIK (0.4 + 0.45 = 0.85rem), és ezekben a szakaszokban kétszer akkora lenne a cím→tartalom
   távolság, mint az inspektor mezőcsoportjaiban — pedig ugyanaz a viszony. */
.hmi-bgcard > .hmi-leg,
.hmi-railsec > .hmi-leg {
    margin-bottom: 0;
}

/* A sáv ALSÓ HATÁRÁRA nyomva, nem az előtte lévő szakasz alá. A panel-beállítás nem a háttérképhez
   tartozik — attól, hogy utána következik, még nem a folytatása. Az alsó élhez tapadva viszont
   ránézésre is más kategória: „ez a panelé, nem a most épülő tartalomé".

   `margin-top: auto` egy oszlop-flexben: felszívja a maradék helyet. Ha a sáv épp nem magasabb a
   tartalmánál (nagyon alacsony ablak), a szabály magától hatástalan — nem lóg ki és nem takar. */
.hmi-railsec--bottom {
    margin-top: auto;
}

/* A képarányt inline stílus adja (a képé), a magasságot tehát a szélesség határozza meg. */
.hmi-bgcard__thumb {
    display: block;
    width: 100%;
    object-fit: cover;
    border-radius: 6px;
    border: 1px solid var(--c-border-subtle);
}

.hmi-bgcard__info {
    font-size: .75rem;
    color: var(--muted-clr);
    font-variant-numeric: tabular-nums;
}

/* `wrap`: a keskeny bal sávban a „Kép cseréje" felirat mellé nem mindig fér oda az eltávolítás
   ikon-gombja — akkor inkább alá csússzon, mint hogy összenyomódjon. */
.hmi-bgcard__row {
    display: flex;
    gap: .4rem;
    flex-wrap: wrap;
}

/* Egy sorban álló gombok: osszák el a sávot egymás közt (a `width: 100%` itt nem elég). */
.hmi-bgcard__row .hmi-fullbtn {
    flex: 1 1 6rem;
}

.hmi-bgcard__hint {
    margin: 0;
    font-size: .72rem;
    font-style: italic;
    color: var(--muted-clr);
    line-height: 1.35;
}

/* FÁTYOL-CSÚSZKA. A címke mellett kiírt százalék nem dísz: a csúszka pozíciója egy 13rem-es sávban
   nem olvasható le pontosan, márpedig egy panel átvétele („mennyi is volt itt beállítva?") ezen
   múlik. `tabular-nums`, hogy a szám húzás közben ne ugráljon a saját szélessége miatt.

   A címke-sor itt flex, hogy a név balra, a szám jobbra üljön. SZŰKEN a .hmi-bgcard-ra kötve: a
   .hmi-insp__lbl az inspektor osztálya, azt globálisan átírni minden mezőjét érintené. */
.hmi-bgcard .hmi-insp__lbl {
    display: flex;
    justify-content: space-between;
    gap: .4rem;
}

.hmi-bgcard__val {
    color: var(--text-clr);
    font-variant-numeric: tabular-nums;
}

/* `accent-color`: a natív csúszka különben a böngésző saját kékjével rajzolódna, ami egyik témánk
   márkaszínével sem egyezik — és night módban kifejezetten kilógna a vörös-eltolt palettából. */
.hmi-bgcard__range {
    width: 100%;
    min-width: 0;
    margin: .1rem 0 0;
    accent-color: var(--primary-clr);
    cursor: pointer;
}

.hmi-bgcard__range:focus-visible {
    outline: 2px solid var(--primary-clr);
    outline-offset: 2px;
}

/* ----------------------------------------------------------------------------
   BILLENTYŰ-ÚTMUTATÓ
   -------------------------------------------------------------------------- */

/* Referencia, nem munkaeszköz. A szerkesztő-blokkban SAJÁT RÁCSTERÜLETE van (`keys`), az elhelyezését
   tehát a responszív szakasz dönti el a lap alján: széles ablakban a vászon alatt, keskenyben a
   legvégén. (Volt a fejlécben, ahol a név és a leírás mellett a legszűkebb a hely; volt a bal sávban,
   ahol egyetlen sora sem fért ki; és volt a munkaterületen BELÜL, ahonnan viszont keskeny ablakban a
   vászon és az inspektor KÖZÉ ékelődött.)
   Az itteni értékek a blokkon KÍVÜLI használatra valók — a blokkon belül mind felülíródik. */
.hmi-keys {
    margin-top: .75rem;
    padding: .55rem .7rem;
    background: var(--list-bg);
    border-radius: var(--list-border-radius);
    font-size: .78rem;
}

.hmi-keys__sum {
    font-size: .66rem;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--muted-clr);
    cursor: pointer;
    user-select: none;
}

/* Billentyű | jelentés párok. A `dt` a tartalomhoz igazodik, a `dd` viszi a maradékot — így a
   leírások egy vonalban kezdődnek. A vászon alatt van hely KÉT párra soronként; keskenyebb ablakban
   (lásd lentebb) egyre esik vissza. */
.hmi-keys__list {
    display: grid;
    grid-template-columns: repeat(2, auto 1fr);
    gap: .35rem 1.25rem;
    margin: .5rem 0 0;
}

.hmi-keys__list dt {
    white-space: nowrap;
}

/* ⚠️ 760px és nem 1100: az útmutató saját rácsterületet kapott, tehát 1100px alatt már a TELJES
   szélességet megkapja — ott bőven elfér két pár egy sorban. Egy oszlopra csak akkor kell váltania,
   amikor tényleg szűk a lap. */
@media (max-width: 760px) {
    .hmi-keys__list {
        grid-template-columns: auto 1fr;
    }
}

.hmi-keys__list dd {
    margin: 0;
    color: var(--muted-clr);
    line-height: 1.3;
}

.hmi-keys kbd {
    display: inline-block;
    padding: 1px 5px;
    font-family: inherit;
    font-size: .72rem;
    font-weight: 600;
    color: var(--text-clr);
    background: var(--c-surface-elevated);
    border: 1px solid var(--c-border-subtle);
    border-radius: 4px;
}

/* ⚠️ --muted-clr és NEM --extra-muted-clr: ez a sor mondja meg, hogy a parancsok a VÁSZONRA szólnak
   — enélkül a felhasználó azt hiszi, hogy nem működnek. A legfontosabb sor nem lehet a legnehezebben
   olvasható. (Ugyanez a hiba volt az inspektor üres állapotának tippjén.)
   A dőlt szedés marad: az különbözteti meg a megjegyzést a felsorolás tartalmától. */
.hmi-keys__hint {
    margin: .5rem 0 0;
    font-size: .74rem;
    font-style: italic;
    color: var(--muted-clr);
    line-height: 1.35;
}

/* ----------------------------------------------------------------------------
   INSPEKTOR — a kijelölt elem tulajdonságai (szerkesztő mód)
   -------------------------------------------------------------------------- */
/* Nem önálló kártya többé: a jobb oldalsáv alsó szakasza. A keretét a szerkesztő-blokk viszi. */
.hmi-insp {
    display: grid;
    gap: 1rem;
    min-width: 0;
    padding: .75rem;
    font-size: .82rem;
}

.hmi-insp__head {
    display: flex;
    align-items: baseline;
    gap: .4rem;
    flex-wrap: wrap;
}

.hmi-insp__title {
    font-size: .8rem;
    font-weight: 700;
    color: var(--text-clr);
}

.hmi-insp__kind {
    font-size: .75rem;
    font-weight: 600;
    color: var(--primary-clr);
}

.hmi-insp__id {
    margin-left: auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .72rem;
    color: var(--muted-clr);
}

.hmi-insp__empty {
    margin: 0;
    color: var(--muted-clr);
}

/* A cselekvésre hívó sor („kattints egy elemre") — ez viszi tovább a felhasználót, tehát olvashatónak
   kell lennie. A --extra-muted-clr mindhárom témában túl halvány volt hozzá. */
.hmi-insp__hint {
    display: block;
    margin-top: .25rem;
    font-size: .76rem;
    font-style: italic;
    color: var(--muted-clr);
}

.hmi-insp__note {
    margin: 0;
    font-size: .78rem;
    color: var(--muted-clr);
}

/* A vezeték két vége közti nyíl. Hangsúlyos, mert ez mondja meg az ÁRAMLÁS irányát — a két
   elem-név mellett a nyíl az egyetlen dolog, ami megfordul, amikor átállítod. */
.hmi-insp__arrow {
    margin: 0 .35em;
    color: var(--text-clr);
    font-weight: 600;
}

/* KÖTÉS-GOMB: úgy néz ki, mint egy beviteli mező, de dialógust nyit. Azért mező-kinézetű, mert az
   inspektorban a többi sorral egy rácsban ül — egy „gombos" külső kilógna a ritmusból. */
.hmi-insp__pick {
    display: flex;
    align-items: center;
    gap: .4rem;
    text-align: left;
    cursor: pointer;
}

/* A felirat hosszú is lehet (eszköz · metrika), a sáv viszont keskeny: egy sorban marad, és a végén
   elvágódik — a teljes nevet úgyis a dialógus mutatja. A min-width:0 nélkül a flex-elem nem enged
   zsugorodni, és a chevron kicsúszna a mezőből. */
.hmi-insp__pick-text {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ÉLŐ VISSZAJELZÉS a kiválasztott kötésről (állapot-szó + aktuális olvasat).
   A riasztás-üzenet egész mondat is lehet, a sáv viszont keskeny: törjön bárhol, ne feszítse szét. */
/* ⚠️ JOBBRA IGAZÍTVA (felhasználói kérés). Ez a sor nem egy újabb mező, hanem a FÖLÖTTE lévő
   kötés-gomb lábjegyzete — balra zárva a mezőcímkék oszlopába állt be, és egy negyedik szintként
   olvasódott. A jobb szélhez húzva ránézésre látszik, hogy a gombhoz tartozik, és nem tolja el a
   szemet a címkék függőleges vonaláról. A `text-align` a TÖRDELT sor miatt kell: a flex csak a
   dobozokat tolja jobbra, a bennük több sorba tört szöveget nem. */
.hmi-insp__live {
    display: flex;
    align-items: baseline;
    justify-content: flex-end;
    gap: .35rem;
    margin: -.15rem 0 0;
    font-size: .78rem;
    text-align: right;
    overflow-wrap: anywhere;
}

/* A szín SZEREPBŐL jön (inline style), ugyanabból a --hmi-* rétegből, amit a vászon is használ —
   így a szerkesztőben látott állapot és a panelen látott szín nem tud elcsúszni egymástól. */
.hmi-insp__live-state {
    font-weight: 600;
    white-space: nowrap;
}

.hmi-insp__live-text {
    color: var(--muted-clr);
    min-width: 0;
}

/* A <fieldset> itt CSAK szemantikai csoport: a <legend> nevezi meg a benne lévő mezőket. A saját
   kerete és paddingja lekerül róla, a rácsot egy belső <div> adja — így a fieldset eltérő
   böngésző-viselkedése (display:grid/flex fieldset-en) fel sem merül.
   ⚠️ `min-width: 0`: a fieldset alapértelmezett `min-width: min-content`-je megakadályozná, hogy a
   sáv keskeny ablakban összemenjen. */
.hmi-insp__grp {
    margin: 0;
    padding: 0;
    border: 0;
    min-width: 0;
}

/* ⚠️ A SZAKASZCÍM AHHOZ TARTOZZON, AMIT MEGNEVEZ. A hajszálvonal a csoport TETEJÉN ül, tehát az
   előző szakaszt zárja le. A szakaszok KÖZÖTT a konténer 1rem-es `gap`-je ad távolságot, a címtől a
   saját mezőiig viszont csak .25rem — annyi, mint a rács sorköze. A cím tehát ránézésre a lentebbi
   blokk része, nem egy önálló, félúton lebegő sor. */
.hmi-insp__grp + .hmi-insp__grp {
    border-top: 1px solid var(--c-border-subtle);
}

/* display/float/width: a <legend> alapból a fieldset keretébe ágyazott, különleges elhelyezésű elem —
   keret nélkül sima blokk-címként akarjuk. A `.hmi-leg` ugyanez sima <span>-en (bal sáv csoportjai,
   háttérkép-szakasz), hogy a blokk minden szakaszcíme egyformán nézzen ki. */
.hmi-insp__leg,
.hmi-leg {
    display: block;
    float: none;
    width: 100%;
    padding: 0;
    margin-bottom: .4rem;
    font-size: .66rem;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--muted-clr);
}

/* ⚠️ CSAK az inspektoré, a bal sáv `.hmi-leg`-jéé NEM: ott a konténer `gap`-je adja a térközt (lásd
   a .hmi-railsec fenti megjegyzését), tehát a margó csökkentése ott nem javítana, csak szűkítene.
   Itt a cím→mezők táv szándékosan azonos a rács sorközével: a cím így a saját blokkjának első sora,
   nem egy külön szint a két csoport között. */
.hmi-insp__leg {
    margin-bottom: .25rem;
}

/* MAGYARÁZAT-JELVÉNY (a közös HelpTip „?" gombja) az inspektor mezői mellett.

   ⚠️ Miért a HelpTip és nem saját tooltip: a buborékja `position: fixed` + `clamp()`, tehát SEM a
   görgethető oldalsáv overflow-ja, SEM a képernyő széle nem vágja le. Egy anchor-alapú JS-tooltip
   (FluentTooltip) itt a jobb szélen kilógott — az inspektor a lap legszélső oszlopa.

   ⚠️ A modbus.css KÉSŐBB töltődik, mint ez a fájl, tehát a méret-felülíráshoz nagyobb fajsúly kell:
   innen a `.hmi-shell` előtag. A jelvény alapból 1.05rem, ami a .66rem-es szakaszcímek és a .68rem-es
   mezőcímkék mellett aránytalanul nagy. A szerkesztő-blokk EGÉSZÉRE szól, nem csak az inspektorra:
   a bal elem-sáv szakaszcímei mellett is ugyanez a jelvény áll. */
.hmi-shell .help-tip {
    margin-left: .25rem;
}

.hmi-shell .help-tip__btn {
    width: .9rem;
    height: .9rem;
    font-size: .64rem;
}

.hmi-insp__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.25rem;
}

/* ----------------------------------------------------------------------------
   IGAZÍTÁS ÉS ELOSZTÁS (többes kijelölés, az inspektorban)
   -------------------------------------------------------------------------- */
.hmi-arr {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* NÉGY oszlop: három igazítás + egy elosztás soronként. A két sor a két tengely — így a gomb HELYE
   is elárulja, mit csinál, nem csak a glifája. */
.hmi-arr__grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: .25rem;
}

.hmi-arr__btn {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: .3rem 0;
    background: none;
    border: 1px solid var(--c-border-subtle);
    border-radius: 6px;
    cursor: pointer;
}

.hmi-arr__btn:hover:not(:disabled) {
    background: color-mix(in srgb, var(--primary-clr) 12%, transparent);
    border-color: var(--primary-clr);
}

.hmi-arr__btn:focus-visible {
    outline: 2px solid var(--primary-clr);
    outline-offset: -2px;
}

/* Az elosztás háromnál kevesebb elemre értelmetlen (nincs közbülső, amit mozgatni lehetne) —
   tiltva jelenik meg, nem elrejtve: eltüntetve a rács átrendeződne a kijelölés méretétől függően. */
.hmi-arr__btn:disabled {
    opacity: .4;
    cursor: not-allowed;
}

/* ⚠️ A festés ITT van, nem `currentColor`-ral: az app.css a div/span/p elemekre színt erőltet, tehát
   egy span-be csomagolt ikon öröklése nem megbízható — az <svg> és a gyerekei viszont nem esnek abba
   a szabályba. Ugyanaz a megfontolás, mint a bal sáv glifáinál. */
.hmi-arr__ico {
    width: 20px;
    height: 20px;
    fill: none;
    stroke: var(--muted-clr);
    stroke-width: 1.4;
    stroke-linecap: round;
}

/* Az „elemek" tömörek, a vonatkoztatási vonal körvonalas — a kettő így egy pillantással szétválik. */
.hmi-arr__f {
    fill: var(--muted-clr);
    stroke: none;
}

.hmi-arr__btn:hover:not(:disabled) .hmi-arr__ico {
    stroke: var(--primary-clr);
}

.hmi-arr__btn:hover:not(:disabled) .hmi-arr__f {
    fill: var(--primary-clr);
}

.hmi-insp__f {
    display: flex;
    flex-direction: column;
    gap: .12rem;
    min-width: 0;
}

.hmi-insp__f--wide {
    grid-column: 1 / -1;
}

/* A címke szabadon tördelhet (a hosszabbak nem férnek ki egy sorba egy fél oszlopban), a mezőt
   viszont a cella ALJÁRA toljuk — így egy sorban a mezők akkor is egy vonalban maradnak, ha az egyik
   címke két sorra tört. (A szín-minták sora ugyanezt a saját `margin-top: auto`-jával kapja meg.) */
.hmi-insp__f > .hmi-insp__in {
    margin-top: auto;
}

.hmi-insp__lbl {
    font-size: .68rem;
    line-height: 1.25;
    color: var(--muted-clr);
}

.hmi-insp__in {
    width: 100%;
    min-width: 0;
    padding: .22rem .35rem;
    font: inherit;
    font-size: .78rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-clr);
    /* A kártya felülete fölött a mezőnek MÁSNAK kell lennie, különben csak a hajszálvékony keret
       választaná el — az „elevated" mind a három témában eltér a kártya felületétől. */
    background: var(--c-surface-elevated);
    border: 1px solid var(--c-border-subtle);
    border-radius: 6px;
}

.hmi-insp__in:focus {
    outline: 2px solid var(--primary-clr);
    outline-offset: -1px;
}

/* ⚠️ A JELÖLŐNÉGYZET MINDIG SAJÁT SORT KAP (`grid-column: 1 / -1`), és ez nem ízlés-kérdés.

   Fél oszlopban a hosszabb feliratok („Card background", „Pulse on alarm") KÉT SORBA törtek, a
   szomszédos cella egysoros maradt, és a lista fűrészfogas lett — ránézésre nem lehetett végigfutni
   rajta, hogy mi van bekapcsolva. A számmezők párba állítása hasznos (rövidek és összetartoznak), a
   kapcsolóké nem: azok egy FÜGGŐLEGES LISTA, amit végig kell olvasni.

   Szabályként, nem osztályonként kiírva: így egy új jelölőnégyzetnél nem lehet elfelejteni. */
.hmi-insp__chk {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: .4rem;
    min-width: 0;
    color: var(--text-clr);
    cursor: pointer;
}

/* A natív jelölőnégyzet aránytalanul nagy a .78rem-es mezők mellett. */
.hmi-insp__chk input {
    width: 15px;
    height: 15px;
    margin: 0;
    accent-color: var(--primary-clr);
    cursor: pointer;
}

/* SZÍN-SZEREP MINTÁK. A minta a szerep tényleges színét mutatja, ugyanabból a --hmi-* rétegből, amit
   a vászon is használ — témát váltva együtt mozog vele.

   A `gap` nem csak levegő: a kiválasztott minta gyűrűje kifelé nő, és ennél szűkebben rálógna a
   szomszédjára. */
.hmi-swatches {
    display: flex;
    flex-wrap: wrap;
    gap: 7px;
    margin-top: auto;
}

.hmi-sw {
    width: 22px;
    height: 22px;
    padding: 0;
    background: none;
    border: 1px solid var(--c-border-subtle);
    border-radius: 6px;
    cursor: pointer;
}

.hmi-sw:hover {
    border-color: var(--primary-clr);
}

.hmi-sw:focus-visible {
    outline: 2px solid var(--primary-clr);
    outline-offset: 2px;
}

/* A KIVÁLASZTOTT: gyűrű a minta KÖRÜL, nem rajta. A magát a színt nem szabad megváltoztatni a
   jelöléstől — épp azt kell mutatnia, amit kapni fogsz. A belső, felület-színű gyűrű választja el a
   mintát a hangsúly-gyűrűtől, hogy sötét mintán is látszódjon a határ. */
.hmi-sw--on {
    box-shadow: 0 0 0 2px var(--list-bg), 0 0 0 4px var(--primary-clr);
}

/* A „nincs" nem szín, hanem a festés hiánya — átlós vonal a szokásos jelölése. Egy egyszerűen
   átlátszó négyzet a felülettől függően vagy fehérnek, vagy semminek látszana. */
.hmi-sw--none {
    background:
        linear-gradient(to top right,
            transparent calc(50% - 1px),
            var(--extra-muted-clr) calc(50% - 1px),
            var(--extra-muted-clr) calc(50% + 1px),
            transparent calc(50% + 1px));
}

/* MINI-VIZ MINTÁK. Ugyanaz a gomb, mint a szín-mintáé (méret, keret, kiválasztás-gyűrű), csak a
   tartalma más: ott a szín MAGA a háttér, itt egy kirajzolt alakzat.

   Nagyobb, mint a szín-minta: egy 22 px-es dobozban egy iránytű és egy mérőóra
   megkülönböztethetetlen lenne. A szín egyetlen pixelből is felismerhető, a forma nem. */
.hmi-sw--viz {
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    background: var(--c-surface-sunken, var(--c-surface-elevated));
}

.hmi-sw__viz {
    width: 22px;
    height: 22px;
    /* Nem négyzetes alakoknál (hőmérő, mérőóra) a viewBox tartja a képarányt — a `contain`
       gondoskodik róla, hogy a doboz ne nyújtsa meg. */
    object-fit: contain;
    overflow: visible;
}

/* Az „automatikus" gomb: betű, nem alakzat. Az auto alakzatát kirajzolva megkülönböztethetetlen
   lenne attól a gombtól, ami ugyanazt az alakot RÖGZÍTI — pedig a kettő mást jelent (a rögzített
   nem követné, ha a jel osztályozása változik). */
.hmi-sw--auto {
    width: 32px;
    height: 32px;
    font-size: .8rem;
    font-weight: 700;
    line-height: 1;
    color: var(--muted-clr);
    background: var(--c-surface-sunken, var(--c-surface-elevated));
}

/* Kiegészítő sor egy dialógusban (pl. „hány elem vész el a törléssel"). */
.hmi-dialog__note {
    margin: .35rem 0 0;
    font-size: .85rem;
    color: var(--muted-clr);
}

/* ============================================================================
   RESPONSZÍV ELRENDEZÉS — a szerkesztő-blokk töréspontjai

   ⚠️ EZ A SZAKASZ A FÁJL VÉGÉN VAN, ÉS OTT IS KELL MARADNIA. A `@media` blokk NEM emel fajsúlyt:
   azonos szelektornál a KÉSŐBBI szabály nyer. Feljebb állva a blokk pont azokat az alapszabályokat
   nem tudta felülírni, amiket akart (`.hmi-rail`, `.hmi-bgcard`, `.hmi-railsec`) — a felső sávban
   ezért maradtak egymás ALATT a csoportok, és ezért maradt rajtuk a szakasz-elválasztó vonal.

   Három lépcső, és mindegyik határ MÉRT, nem kerekített:
     • ≥1400px — bal sáv | vászon | inspektor. 13 + 17rem sáv alatt ennél keskenyebben a vászonnak
       kevesebb hely marad, mint amennyi egy panel ÁTTEKINTÉSÉHEZ kell (a nagyítás ezen nem segít:
       az a részleteket hozza közelebb, nem az egészet).
     • 1100–1400px — a bal sáv fölülre kerül vízszintes csíkként, OSZLOPOKBAN; az inspektor a vászon
       mellett marad. Azért az inspektor marad: azt minden kijelölés után nézed, a hozzáadó sávhoz
       viszont csak alkalmanként nyúlsz — a ritkábban használtat éri meg vízszintesbe fordítani.
     • <1100px — a vászon mellett MÁR SEMMI. Ekkora szélességen bármi, ami mellette áll, elveszi a
       helyet attól, amiért az egész felület van.
   ========================================================================= */

/* A billentyű-útmutató a törzs-rács önálló eleme. Széles ablakban a vászon ALATT ül (a vele közös
   munkafelületen), keskenyben a LEGVÉGÉN — az inspektort kell a vászon közelében tartani, az
   útmutató referencia. */
.hmi-shell__body--edit > .hmi-keys {
    grid-area: keys;
    margin: 0;
    padding: 0 .75rem .75rem;
    background: var(--c-surface-elevated);
    border-radius: 0;
}

@media (max-width: 1400px) {
    .hmi-shell__body--edit {
        grid-template-columns: minmax(0, 1fr) 15rem;
        grid-template-areas:
            "rail rail"
            "work side"
            "keys side";
        /* Ugyanaz az elv, mint a három oszlopnál: a vászon sora tartalommagasságú, a maradékot az
           útmutató sora nyeli el — így az közvetlenül a vászon alatt kezdődik. Itt már csak az
           inspektor fog át két sort. */
        grid-template-rows: auto auto 1fr;
    }

    /* A sáv szakaszai (elemcsoportok, háttérkép, panel-beállítás) NÉGY OSZLOPBAN, egymás mellett. */
    .hmi-shell__rail {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: flex-start;
        gap: .8rem 1.5rem;
        border-right: 0;
        border-bottom: 1px solid var(--c-border-subtle);
    }

    /* A két elemcsoport (Kijelzés, Rajz) is EGYMÁS MELLÉ kerül — ez a két oszlop a négyből. */
    .hmi-rail {
        flex-direction: row;
        align-items: flex-start;
        gap: 1.5rem;
    }

    .hmi-rail__grp {
        min-width: 8.5rem;
    }

    /* Vízszintesen a szakaszok már nem egymás ALATT vannak: a felső hajszálvonal és az aljára
       nyomás (margin-top: auto) itt félrevezető lenne — az elválasztást a térköz adja. */
    .hmi-bgcard,
    .hmi-railsec {
        min-width: 11rem;
        max-width: 15rem;
        padding-top: 0;
        border-top: 0;
    }

    .hmi-railsec--bottom {
        margin-top: 0;
    }
}

@media (max-width: 1100px) {
    .hmi-shell__body--edit {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas:
            "rail"
            "work"
            "side"
            "keys";
        /* ⚠️ VISSZAÁLLÍTVA. Egy oszlopban semmi nem fog át több sort, tehát nincs szétosztandó
           többlet — a szélesebb elrendezésekből örökölt `1fr` viszont feleslegesen felfújná az
           utolsó sort a lap aljáig. */
        grid-template-rows: none;
    }

    /* Az inspektor a vászon ALÁ csúszik — ezért marad a kijelölés-kijelző az eszközsávban is: ott
       akkor is látszik, mi van kijelölve, amikor az inspektor fejléce már nincs szem előtt. */
    .hmi-shell__side {
        border-left: 0;
        border-top: 1px solid var(--c-border-subtle);
    }

    /* A legvégére kerülve az útmutató már nem a vászon folytatása: kapjon saját elválasztót. */
    .hmi-shell__body--edit > .hmi-keys {
        padding: .6rem .75rem .75rem;
        border-top: 1px solid var(--c-border-subtle);
    }
}
