/* ===========================================================================
   CoreDesk — komponente po MD3 specifikaciji
   Vlastite i tanke; Googleove web komponente su odbačene (SCOPE §3.2).
   =========================================================================== */

/* ═══ Gumbi ═══════════════════════════════════════════════════════════════
   MD3 ima pet varijanti. Svaka ima "state layer" — poluprozirni sloj boje
   koji se pojavljuje na hover/focus/press. To je ono što gumbima daje
   materijalni osjećaj umjesto obične promjene boje.                        */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  min-height: 40px;
  padding: 0 1.5rem;
  border: none;
  border-radius: var(--shape-full);
  font: var(--type-label-lg);
  letter-spacing: .01em;
  cursor: pointer;
  overflow: hidden;
  text-decoration: none;
  transition: box-shadow var(--dur-short) var(--ease-standard);
  -webkit-tap-highlight-color: transparent;
}
.btn::before {
  content: '';
  position: absolute;
  inset: 0;
  background: currentColor;
  opacity: 0;
  transition: opacity var(--dur-short) var(--ease-standard);
  pointer-events: none;
}
.btn:hover::before  { opacity: var(--state-hover); }
.btn:focus::before  { opacity: var(--state-focus); }
.btn:active::before { opacity: var(--state-pressed); }
.btn:hover { text-decoration: none; }
.btn:disabled, .btn[aria-disabled="true"] {
  cursor: not-allowed;
  opacity: .38;
  box-shadow: none;
}
.btn:disabled::before { opacity: 0; }
.btn .icon { font-size: 1.125rem; margin-left: -.25rem; }

/* Filled — glavna radnja */
.btn.filled, .btn:not([class*=" "]):not(.tonal):not(.outlined):not(.text):not(.elevated) {
  background: var(--md-primary);
  color: var(--md-on-primary);
}
.btn.filled:hover { box-shadow: var(--elev-1); }

/* Tonal — sekundarna radnja, pastelna */
.btn.tonal {
  background: var(--md-secondary-container);
  color: var(--md-on-secondary-container);
}
.btn.tonal:hover { box-shadow: var(--elev-1); }

/* Elevated */
.btn.elevated {
  background: var(--md-surface-container-low);
  color: var(--md-primary);
  box-shadow: var(--elev-1);
}
.btn.elevated:hover { box-shadow: var(--elev-2); }

/* Outlined */
.btn.outlined {
  background: transparent;
  color: var(--md-primary);
  border: 1px solid var(--md-outline-variant);
}

/* Text */
.btn.text {
  background: transparent;
  color: var(--md-primary);
  padding: 0 .75rem;
}

/* Destruktivna */
.btn.danger { background: var(--md-error); color: var(--md-on-error); }

.btn.sm { min-height: 32px; padding: 0 1rem; font: var(--type-label-md); }
.btn.lg { min-height: 48px; padding: 0 1.75rem; }
.btn.block { width: 100%; }

/* Icon button */
.icon-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  border: none;
  border-radius: var(--shape-full);
  background: transparent;
  color: var(--md-on-surface-variant);
  cursor: pointer;
  overflow: hidden;
}
.icon-btn::before {
  content: ''; position: absolute; inset: 0;
  background: currentColor; opacity: 0;
  transition: opacity var(--dur-short) var(--ease-standard);
}
/* Podcrtavanje je znak POVEZNICE U TEKSTU, a ne kontrole. Sve što izgleda kao
   gumb ostaje bez njega i kad je izvedeno kao <a> — inače krug na hoveru
   dobije crtu ispod teksta/ikone. */
.icon-btn, .icon-btn:hover,
.chip, .chip:hover,
.badge, .badge:hover { text-decoration: none; }

.icon-btn.sm { width: 28px; height: 28px; }
.icon-btn.sm .icon { font-size: 1.125rem; }
.icon-btn:hover::before  { opacity: var(--state-hover); }
.icon-btn:active::before { opacity: var(--state-pressed); }
.icon-btn.filled { background: var(--md-primary-container); color: var(--md-on-primary-container); }

.actions { display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; margin-top: 1.5rem; }

/* ── `.save-state` — ishod spremanja UZ GUMB ─────────────────────────────
   Poruka umetnuta iznad sekcije gurne sve ispod sebe kad se pojavi, pa opet
   kad nestane — dva pomaka za jednu potvrdu. Ovdje stoji u istom retku kao
   gumb, pa se okomito ne miče ništa.

   Uz gumb je ujedno i NAJUOČLJIVIJA: ondje je pogled u trenutku klika, a kod
   dužeg obrasca vrh sekcije je već odskrolao s ekrana.

   Nestaje prozirnošću, ne uklanjanjem — `display:none` bi vratio onaj isti
   pomak koji izbjegavamo.

   Boja je MODIFIKATOR po ishodu, a ne stanje samog bloka: bez oznake je poruka
   tijeka („Spremanje…") i ostaje prigušena — ona ništa ne tvrdi, pa nema što
   ni obojiti.

   🔑 POTVRDA KORISTI `--md-success-light` (#43AD56), ne `--md-success`.
   Zagasita zelena je na ovoj količini teksta prezagasita — potvrda se stopi s
   okolinom i promakne. Svjetlija je na bijeloj kartici mjerena na 2,9:1, dakle
   **ispod AA praga**, i to je svjestan ustupak: riječ je o kratkoj potvrdi od
   jedne riječi koja se ionako gasi, a ne o sadržaju koji se čita. Provjereno
   na ekranu prije odluke.

   Ispuna ikone (`FILL 1`) nosi glavninu signala: puna ploha se na ovoj
   veličini vidi prije nego kontura ili nijansa teksta. Isti token na isti
   način koriste `.icon-btn.copied` i `.note.has-note`.

   ⚠️ GREŠKA OSTAJE na `--md-error` (6,5:1). Ona je jedina poruka koja se
   stvarno mora **pročitati** — i jedina koja ne nestaje sama.                */

.save-state {
  display: inline-flex; align-items: center; gap: .3rem;
  font: var(--type-body-sm);
  color: var(--md-on-surface-variant);
  opacity: 0;
  transition: opacity var(--dur-short) var(--ease-standard);
}
.save-state.show    { opacity: 1; }
.save-state.success { color: var(--md-success-light); }
.save-state.warning { color: var(--md-warning); }
.save-state.error   { color: var(--md-error); }

.save-state .icon { font-size: 1.125rem; font-variation-settings: 'FILL' 1; }

/* ═══ Kartice ════════════════════════════════════════════════════════════ */

.card {
  background: var(--md-surface-container-lowest);
  border-radius: var(--shape-lg);
  padding: 1.5rem;
  border: 1px solid var(--md-outline-variant);
}
.card.elevated { border-color: transparent; box-shadow: var(--elev-1); }
.card.filled   { background: var(--md-surface-container); border-color: transparent; }
.card.tonal    { background: var(--md-primary-container); color: var(--md-on-primary-container); border-color: transparent; }
.card.pad-sm   { padding: 1rem 1.25rem; }
.card.pad-lg   { padding: 2rem; }
.card > :first-child { margin-top: 0; }
.card > :last-child  { margin-bottom: 0; }

.card-head {
  display: flex; align-items: center; gap: .75rem;
  margin-bottom: 1.25rem;
}
.card-head h3 { margin: 0; }
/* Zaglavlje boja SVOJU ikonu — izravno dijete. Ikone unutar ugniježđenih
   blokova (`.badge`, `.btn`) pripadaju tim blokovima i nose njihovu boju;
   ne traže iznimku od roditelja. */
.card-head > .icon { color: var(--md-primary); }

/* ═══ Stat pločica ═══════════════════════════════════════════════════════
   Pastelna okrugla oznaka + veliki broj — jezik iz referenci.              */

.stat {
  background: var(--md-surface-container-lowest);
  border: 1px solid var(--md-outline-variant);
  border-radius: var(--shape-lg);
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: .75rem;
}
.stat-badge {
  width: 40px; height: 40px;
  border-radius: var(--shape-full);
  display: grid; place-items: center;
  background: var(--cat-1-soft);
  color: var(--cat-1);
}
.stat-label { font: var(--type-body-sm); color: var(--md-on-surface-variant); }
/* `line-height: 1.2`, ne 1: brojka ima podnaslov ispod sebe (postotak, „od
   mogućih X"), a uz zbijeni redak mu sjeda pod donje produžetke znamenki. */
.stat-value { font: var(--type-display-sm); letter-spacing: -.02em; line-height: 1.2; }
/* Mjerna jedinica uz brojku. Razmak je `.25rem`, ne manji: uz veliku brojku
   stat-pločice `%` i oznaka valute inače sjednu pretijesno uz zadnju znamenku
   i čitaju se kao njezin dio. */
.stat-value .unit { font: var(--type-title-md); color: var(--md-on-surface-variant); margin-left: .25rem; }

/* Nazivnik NIJE jedinica — „15/20 dana" je jedan broj, ne broj pa oznaka, pa
   razmak ispred kose crte razdvaja ono što se čita zajedno. Izgled (veličina,
   prigušena boja) ostaje isti, miče se samo odmak. */
.stat-value .unit.ratio { margin-left: 0; }
.stat-delta { font: var(--type-label-md); }
.stat-delta.up   { color: var(--md-success); }
.stat-delta.down { color: var(--md-error); }

/* Pokazatelj koji TRAŽI PAŽNJU dok ne dođe na nulu — npr. preostalo za naplatu.
   Isticanje nosi SAMO OKVIR. Bojanje broja je probano i izbačeno: kućice stoje
   u nizu, pa obojen iznos među crnima izgleda kao druga vrsta podatka umjesto
   kao isti podatak u drugom stanju. Okvir kaže isto, a ništa ne prepravlja. */
.stat.fail { border-color: var(--md-error-strong); }

.stat.c1 .stat-badge { background: var(--cat-1-soft); color: var(--cat-1); }
.stat.c2 .stat-badge { background: var(--cat-2-soft); color: var(--cat-2); }
.stat.c3 .stat-badge { background: var(--cat-3-soft); color: var(--cat-3); }
.stat.c4 .stat-badge { background: var(--cat-4-soft); color: var(--cat-4); }
.stat.c5 .stat-badge { background: var(--cat-5-soft); color: var(--cat-5); }
.stat.c6 .stat-badge { background: var(--cat-6-soft); color: var(--cat-6); }

/* ═══ Polja ══════════════════════════════════════════════════════════════
   MD3 outlined text field s labelom koja "sjedi" u obrubu.                 */

.field { position: relative; margin-bottom: 1.25rem; }

.field > label {
  display: block;
  font: var(--type-label-md);
  color: var(--md-on-surface-variant);
  margin-bottom: .4rem;
}

/* ── `.input` — kontrola obrasca ─────────────────────────────────────────
   IZGLED kontrole je vlastiti blok. `.field` je samo RASPORED (labela, razmak,
   hint) i svoje kontrole uključuje u isti izgled — jedna definicija, a ne tri
   puta do istog rezultata. Kontrola izvan `.field` (filter-trake, ćelije
   tablice) nosi klasu `.input` i izgleda identično.
   Veličina je MODIFIKATOR (`.sm`), ne novi kontekst po ekranu.              */

.input,
.field input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="hidden"]),
.field select, .field textarea {
  width: 100%;
  padding: .5rem .9rem;
  background: var(--md-surface-container-lowest);
  color: var(--md-on-surface);
  border: 1px solid var(--md-outline-variant);
  border-radius: var(--shape-sm);
  transition: border-color var(--dur-short) var(--ease-standard),
              box-shadow var(--dur-short) var(--ease-standard);
}

/* 🔑 ZADANA visina, ne najmanja — i to je razlika koja se vidi.
   S `min-height` visinu određuje SADRŽAJ kontrole, a on nije isti u svakoj:
   `<input type="date">` Chrome slaže od unutarnjih dijelova (zasebna polja za
   dan, mjesec i godinu plus gumb kalendara), a oni nose vlastiti razmak koji
   se zbraja. Zato je birač datuma u istom redu ispadao koji piksel viši od
   `<select>`. Zadana visina tu razliku uklanja na izvoru, umjesto da se
   nagađa po pikselima — i vrijedi za svaku kontrolu, ne samo za ovaj red.

   Jednoredne kontrole, dakle: `textarea` zadržava slobodnu visinu. */
.input,
.field input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="hidden"]),
.field select {
  height: var(--control-h);
}

/* Zbijena varijanta — filter-trake i redovi tablice. Ovdje ostaje `min-height`:
   redak teksta ne stane u 36px, pa bi zadana visina odrezala podrepke. */
/* Vodoravni razmak je .5rem, ne .8rem: `.input.sm` živi gotovo isključivo u
   ćelijama tablica, gdje je svaki piksel širine stupac manje. Uspravni ostaje
   isti — on drži visinu retka, a ona se nije mijenjala. */
.input.sm { height: auto; min-height: 36px; padding: .45rem .5rem; }

/* Najmanja širina po TOME ŠTO POLJE DRŽI, a ne po stupcu u kojem stoji.
   🔑 U tablici polje inače poprimi širinu ćelije, a ćelija se stisne na ono što
   joj ostane — pa raspored izgleda zbijen i sadržaj se reže. Mjera je ovdje
   svojstvo podatka: datum uvijek traži isto, postotak najviše tri znaka.
   ⚠️ Datum se u pregledniku renderira na ~153 px, a čitljiv je na 135 — razlika
   je prazan prostor koji u uskoj tablici plaćaju susjedni stupci. */
.input.field-condition { min-width: 145px; }
.input.field-sent      { min-width: 145px; }
/* Mjere su usklađene s užim razmakom `.input.sm` iznad: prazan `dd/mm/yyyy`
   ispisuje SLOVA, koja su šira od znamenki, pa datum i dalje traži najviše. */
.input.field-date      { min-width: 128px; width: 128px; }
/* 117 — da stane i iznos u desecima milijuna, a da ne otima širinu susjedima. */
.input.field-amount    { min-width: 117px; width: 117px; }
/* 🔑 Postotak je CENTRIRAN, ne desno poravnat. Ovdje se ne poravnavaju mjesne
   vrijednosti nego stoje male brojke u uskim kućicama — a njih oko čita kao
   skupinu, pa centar izgleda uredno ondje gdje desni rub izgleda nasumično.
   `text-align` zato stoji ovdje, a ne preko utility klase `.num`: ta dolazi na
   kraju datoteke i nadjačala bi ovo pravilo. */
.input.field-percent   { min-width: 50px; text-align: center; font-variant-numeric: tabular-nums; }

/* Normalizacija, ne izgled: WebKit polju pretrage daje vlastiti udubljeni
   okvir. Namjerno SAMO za `search` — `appearance: none` na `<select>` briše
   strelicu padajućeg izbornika. */
input[type="search"] { appearance: none; -webkit-appearance: none; }

.input:hover,
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--md-outline); }
.input:focus,
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none;
  border-color: var(--md-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--md-primary) 16%, transparent);
}
.input:disabled, .input[readonly],
.field input:disabled, .field input[readonly] {
  background: var(--md-surface-container);
  color: var(--md-on-surface-variant);
}
.field textarea { min-height: 96px; resize: vertical; }
/* 🔑 `.hint` JE VLASTITI BLOK, kao i `.input` — objašnjenje ispod kontrole,
   ispod tablice ili ispod gumba je ista stvar i mora izgledati isto. Dok je
   pravilo stajalo samo kao `.field .hint`, isti razred izvan obrasca nije
   dobivao NIŠTA: tekst je ostajao u punoj veličini tijela i čitao se kao
   sadržaj, a ne kao uputa. `.field` zadržava samo RASPORED (odmak), jer je on
   doista njegov. */
.hint { font: var(--type-body-sm); color: var(--md-on-surface-variant); }
.field .hint { margin-top: .35rem; }
.hint.error, .field .hint.error { color: var(--md-error); }
/* 🔑 NAPOMENA O OBRASCU, NE O POLJU. Odmak `.field .hint` pripada polju iznad
   njega, pa ga napomena koja stoji ispod GUMBA ne dobiva — i zalijepi se uz
   njega, kao da je natpis gumba. Imenovana je onim što jest (napomena uz
   obrazac), a ne mjestom na kojem se zatekla, pa vrijedi svugdje gdje obrazac
   ima rečenicu o sebi. Razmak je veći od onog uz polje: ovdje se odvaja od
   RADNJE, a ne od kontrole. */
.hint.form-note { margin-top: .75rem; }
.field.invalid input, .field.invalid select { border-color: var(--md-error); }

.field .counter { float: right; font: var(--type-body-sm); color: var(--md-on-surface-variant); }
.counter.near { color: var(--md-warning); font-weight: 600; }

/* Brojač bez labele — LEBDI nad donjim rubom polja i ne sudjeluje u rasporedu,
   pa ne može pomaknuti ništa oko sebe. Pojavi se tek na fokus ili blizu
   granice, da u gustoj tablici ne smeta dok nije potreban.
   Traži roditelja s `position: relative` (v. `.table td`).                  */
.counter.inline {
  position: absolute;
  right: 1.15rem;
  bottom: .15rem;
  padding: 0 .25rem;
  background: var(--md-surface-container-lowest);
  border-radius: var(--shape-xs);
  font: var(--type-body-sm);
  color: var(--md-on-surface-variant);
  text-align: right;
  opacity: 0;
  transition: opacity var(--dur-short) var(--ease-standard);
  pointer-events: none;
}
.counter.inline.show { opacity: 1; }

/* Checkbox / radio */
.check {
  display: flex; align-items: flex-start; gap: .7rem;
  padding: .5rem 0;
}
.check input { margin: .2rem 0 0; width: 18px; height: 18px; accent-color: var(--md-primary); flex: 0 0 auto; }
.check label { font: var(--type-body-lg); color: var(--md-on-surface); cursor: pointer; margin: 0; }
.check .hint { display: block; font: var(--type-body-sm); color: var(--md-on-surface-variant); margin-top: .1rem; }

/* Izbor u obliku kartice (npr. način kreiranja baze) */
.choice {
  display: flex; gap: .75rem; align-items: flex-start;
  padding: 1rem;
  border: 1px solid var(--md-outline-variant);
  border-radius: var(--shape-md);
  cursor: pointer;
  transition: background var(--dur-short) var(--ease-standard),
              border-color var(--dur-short) var(--ease-standard);
}
.choice:hover { background: var(--md-surface-container-low); }
.choice:has(input:checked) {
  border-color: var(--md-primary);
  background: var(--md-primary-container);
  color: var(--md-on-primary-container);
}
/* Odabrani izbor smije nositi boju ZNAČENJA umjesto naglasne. Bilješka koja se
   dijeli s firmom označena je `info` plavom u popisu ispod (`.badge.info`), pa
   mora biti ista i dok se bira — inače izbor i njegov ishod izgledaju kao dvije
   različite stvari.

   🔑 Nosi je MODIFIKATOR, ne vrijednost polja (`:has(input[value="company"])`).
   „Vidi cijela firma" je pojam ove jedne forme; `info` je pojam sučelja, isti
   onaj koji već imaju značka i obavijest. Uz vrijednost bi pravilo vrijedilo
   za svaki budući `value="company"` bilo gdje, a za nijednu drugu plavu.

   Obrub i radio ostaju naglasni namjerno: oni nose „ovo je odabrano", a boja
   plohe „ovo znači" — dvije različite poruke, pa smiju biti dvije boje. */
.choice.info:has(input:checked) {
  background: var(--md-info-container);
  color: var(--md-on-info-container);
}

.choice input { margin-top: .15rem; accent-color: var(--md-primary); }
.choice .t { font: var(--type-title-sm); }
.choice .d { font: var(--type-body-sm); color: var(--md-on-surface-variant); display: block; margin-top: .15rem; }
.choice:has(input:checked) .d { color: inherit; opacity: .8; }

/* ═══ Token koji se kopira ═══════════════════════════════════════════════
   Pločica iz pomoći koja se klikom prepiše u međuspremnik — npr. `{broj}` u
   formatu numeracije ponuda (§7.6.5). Sastavljanje formata time ne traži
   prepisivanje vitičastih zagrada rukom.

   🔑 GUMB, ne `<code>`. Izgled dolazi iz zajedničke definicije u `base.css`,
   pa se ne udvostručuje; ovdje je samo ono što `<code>` ne može — dohvat
   tipkovnicom, jasno stanje fokusa i pokazivač da se u to smije kliknuti.
   `type="button"` je nužan: pomoć stoji unutar obrasca postavki, pa bi zadani
   `submit` spremio postavke umjesto da kopira.

   Potvrdu nosi ploha, a ne zamjena ikone: token je uzak koliko i njegov tekst,
   pa u njega ikona ne stane bez poskakivanja retka. */
.token {
  border: 1px solid transparent;
  color: inherit;
  line-height: inherit;
  cursor: pointer;
  transition: background var(--dur-short) var(--ease-standard),
              border-color var(--dur-short) var(--ease-standard);
}
.token:hover { border-color: var(--md-outline); }
.token.copied {
  background: var(--md-success-container);
  color: var(--md-on-success-container);
  border-color: var(--md-success);
}

/* ═══ Značke i čipovi ════════════════════════════════════════════════════ */

.badge {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .25rem .65rem;
  border-radius: var(--shape-full);
  font: var(--type-label-md);
  background: var(--md-surface-container-high);
  color: var(--md-on-surface-variant);
  white-space: nowrap;
}
.badge .icon { font-size: .9rem; }

/* Značka ispod teksta u istoj ćeliji poravnava se po TEKSTU, ne po svom okviru.
   🔑 Kapsula nosi vlastiti vodoravni razmak (`.65rem`), pa joj slovo počinje
   točno toliko desno od broja iznad — a oko to čita kao uvučen redak, ne kao
   oznaku uz njega. Pomak je zato ISTA mjera kao taj razmak, a ne okrugli broj:
   promijeni li se `padding`, mijenja se na jednom mjestu i ovo ga prati.
   Namjerno `transform`, ne negativna margina: značka ostaje u svom toku, pa ne
   povlači ništa za sobom i ne mijenja visinu retka. */
.badge.flush-text { transform: translateX(-.65rem); }

/* Značka koja nosi SAMO ikonu je krug, ne kapsula: bez teksta kapsula nema
   što opravdati njezinu širinu, pa ispadne razvučena mrlja oko jednog znaka.

   🔑 Visina je FIKSNA, a ne posljedica sadržaja. Bez toga se značka u
   `.row` (koji zadano rasteže djecu) razvuče do visine najvišeg susjeda —
   ista oznaka bila je široka i niska kad je stajala sama, a uska i visoka
   čim bi se pokraj nje pojavio gumb. Fiksna mjera znači isti oblik u oba
   slučaja, bez obzira na to što joj je susjed.

   Ikona ostaje svoje veličine; mijenja se samo ploha oko nje. */
.badge.only-icon {
  width: 28px; height: 28px;
  flex: 0 0 auto;
  padding: 0;
  justify-content: center;
}
.badge.only-icon .icon { font-size: 1.05rem; }

/* Značka SMIJE biti gumb kad ista točka nosi i stanje i radnju (vidljivost
   kontakta zaposlenicima). Izgled ostaje značkin — mijenja se samo to da se
   u nju može kliknuti, pa se ne uvodi drugi oblik za isti pojam. */
button.badge {
  border: none;
  font: var(--type-label-md);
  cursor: pointer;
}

/* Isti odziv bez obzira je li značka gumb ili poveznica — ista stvar pod
   prstom mora se i ponašati isto. Zatamnjenje (u tamnoj temi posvjetljenje,
   v. `--state-brightness`) radi na svih šest varijanti značke, dok bi sloj
   boje svakoj pomaknuo ton drukčije. */
a.badge, button.badge { transition: filter var(--dur-short) var(--ease-standard); }
a.badge:hover, button.badge:hover { filter: brightness(var(--state-brightness)); }
button.badge:focus-visible { outline: 2px solid var(--md-primary); outline-offset: 2px; }

/* Oznaka koja LEBDI nad donjim rubom polja umjesto da stoji ispod njega.
   U tablici bi je redak morao „progutati": povisi svoju ćeliju i pomakne
   polje u odnosu na susjedna. Zato je izvan toka, i bez klika — leži preko
   polja, pa mu ne smije krasti fokus.
   Traži roditelja s `position: relative` (npr. `.picker`, `.table td`). */
.badge.floating {
  position: absolute;
  left: .6rem;
  bottom: -.9rem;
  z-index: 1;
  pointer-events: none;
}
.badge.ok      { background: var(--md-success-container); color: var(--md-on-success-container); }
.badge.warn    { background: var(--md-warning-container); color: var(--md-on-warning-container); }
.badge.fail    { background: var(--md-error-container);   color: var(--md-on-error-container); }
.badge.info    { background: var(--md-info-container);    color: var(--md-on-info-container); }
.badge.primary { background: var(--md-primary-container); color: var(--md-on-primary-container); }
.badge.accent  { background: var(--md-tertiary-container);color: var(--md-on-tertiary-container); }

.chip {
  display: inline-flex; align-items: center; gap: .4rem;
  height: 32px; padding: 0 .85rem;
  border: 1px solid var(--md-outline-variant);
  border-radius: var(--shape-sm);
  background: transparent;
  font: var(--type-label-lg);
  color: var(--md-on-surface-variant);
  cursor: pointer;
}
.chip.selected { background: var(--md-secondary-container); color: var(--md-on-secondary-container); border-color: transparent; }
.chip:hover { background: var(--md-surface-container-low); }
.chip.selected:hover { background: var(--md-secondary-container); }

/* Veličina je MODIFIKATOR, kao kod `.input` — zbijeni čip stoji unutar retka
   teksta (prijedlog u napomeni polja), gdje puna visina razbija redak. */
.chip.sm { height: 26px; padding: 0 .55rem; font: var(--type-label-md); }

/* ── Izbor čipovima ──────────────────────────────────────────────────────
   Isti čip, ali nosi `radio` umjesto poveznice: izbor od nekoliko vrijednosti
   koje stanu u red. Padajući izbornik traži dva klika i skriva ostale
   mogućnosti dok se ne otvori; ovdje su sve vidljive, a odabir je jedan klik.

   Označeno stanje je ISTO kao `.chip.selected` — jedan pojam, jedan izgled,
   bez obzira dolazi li odabir s poveznice ili iz obrasca.                   */

.chipset { display: flex; flex-wrap: wrap; gap: .4rem; }

/* Čipovi koji ZAMJENJUJU kontrolu obrasca i stoje u istom redu s poljima
   moraju biti iste visine kao ona — inače red izgleda kao da je nešto iz
   njega ispalo. Ista brojka za oboje, iz istog tokena. */
.chipset.control .chip { height: var(--control-h); }

/* Skupina čipova koja je VLASTITA OS, a ne nastavak susjednog reda. Vlastita
   ploha i vodeća ikona kažu „ovo pita nešto drugo" prije nego se pročita ijedan
   natpis — u popisu projekata jedan red bira status posla, drugi stanje
   naplate, i bez odvajanja se čitaju kao jedan predugačak niz.

   Ploha je ista kao kod filtra na kartici klijenta (`--filter-surface`): ista
   uloga, isti token, pa tamna tema ide sama i nema fiksne boje koja bi ondje
   ostala svijetla mrlja. */
.chipset.boxed {
  align-items: center;
  padding: .35rem .6rem;
  border-radius: var(--shape-sm);       /* isti oblik kao čipovi unutra */
  background: var(--filter-surface);
}
.chipset.boxed > .icon { color: var(--md-on-surface-variant); margin-inline-end: .1rem; }

.chip:has(input) { position: relative; }

/* Radio ostaje u DOM-u (tipkovnica, čitači ekrana), ali prekriva cijeli čip
   umjesto da se vidi kao kružić — pa je klik bilo gdje po čipu odabir. */
.chip input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }

.chip:has(input:checked) {
  background: var(--md-secondary-container);
  color: var(--md-on-secondary-container);
  border-color: transparent;
}
.chip:has(input:focus-visible) { outline: 2px solid var(--md-primary); outline-offset: 2px; }

/* ═══ Poruke ═════════════════════════════════════════════════════════════ */

.alert {
  display: flex; gap: .85rem; align-items: flex-start;
  padding: 1rem 1.15rem;
  border-radius: var(--shape-md);
  font: var(--type-body-md);
  background: var(--md-surface-container);
  color: var(--md-on-surface);
}
.alert .icon { flex: 0 0 auto; margin-top: -.1rem; }
/* Naslovni redak poruke je VLASTITI POJAM, a ne „prvi podebljani izraz" —
   inače svaka istaknuta riječ usred rečenice postaje blok i lomi tekst. */
.alert-title { display: block; font: var(--type-title-sm); margin-bottom: .15rem; }
.alert.success { background: var(--md-success-container); color: var(--md-on-success-container); }
.alert.error   { background: var(--md-error-container);   color: var(--md-on-error-container); }
.alert.warning { background: var(--md-warning-container); color: var(--md-on-warning-container); }
.alert.info    { background: var(--md-info-container);    color: var(--md-on-info-container); }

/* ═══ Tablice ════════════════════════════════════════════════════════════ */

/* 🔑 BEZ ZAOKRUŽENJA. Okvir koji skrola postoji zbog vodoravnog scrolla, a ne
   zbog oblika — a `border-radius` na njemu REŽE KUTEVE sadržaja: kutnu ćeliju
   mreže, prvi redak tablice. Zaokruženje pripada kartici koja ga okružuje
   (`.card` + `overflow: hidden`), i ondje već postoji; ovdje je bio drugi
   radijus preko prvog. */
.table-wrap { overflow-x: auto; }

.table { width: 100%; border-collapse: collapse; font: var(--type-body-md); }
.table th, .table td {
  text-align: left;
  padding: .8rem .9rem;
  border-bottom: 1px solid var(--md-outline-variant);
  vertical-align: middle;
}
.table th {
  font: var(--type-label-md);
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--md-on-surface-variant);
  white-space: nowrap;
}
.table tbody tr { transition: background var(--dur-short) var(--ease-standard); }
.table tbody tr:hover { background: var(--md-surface-container-low); }
/* Crte nema ispod ZADNJEG RETKA TABLICE — a to nije isto što i zadnji redak
   tijela, čim tablica ima zbrojno podnožje. Ondje crta ispod zadnje stavke
   ODVAJA stavke od zbroja i mora ostati, dok ispod zbroja nema što odvajati.
   Prije je bilo obrnuto: zadnja stavka bez crte, zbroj s njom.

   I `th`, ne samo `td`: u pregledima je labela retka `th` („Dogovoreno",
   „Klijent od"), pa je zadnji redak imao crtu ispod labele, a ne ispod
   vrijednosti. Crta se miče po RETKU, ne po vrsti ćelije. */
.table tbody:last-child tr:last-child td,
.table tbody:last-child tr:last-child th,
.table tfoot tr:last-child td,
.table tfoot tr:last-child th { border-bottom: none; }
.table td.shrink { width: 1%; white-space: nowrap; }

/* Zbrojni redak USRED tablice.
   🔑 Pojam je „ovaj redak je zbroj", a ne „ovaj redak je ovdje" — zato
   modifikator na retku, a ne `tr:nth-child`. Podnožje (`tfoot`) ovdje ne
   pomaže: upitnik ministarstva zbraja i na sredini (5.1.17 stoji između 5.1.16
   i 5.1.18), pa zbrojnih redaka ima više i nisu na kraju.
   Podloga je stepenicu tamnija od one koju nosi `:hover`, da se zbroj ne
   izgubi kad miš prijeđe preko susjednog retka. */
.table tbody tr.is-total { background: var(--md-surface-container); }
.table tbody tr.is-total:hover { background: var(--md-surface-container-high); }
.table tbody tr.is-total td { border-top: 1px solid var(--md-outline-variant); }

/* Podredak — RAZLAGANJE retka iznad sebe, a ne vlastiti unos.
   🔑 Pojam je „ovaj redak pripada gornjemu", ne „ovaj je uvučen" — zato
   modifikator na retku, a uvlaka i prigušenje su njegova POSLJEDICA. Inline
   `padding-left` po ćeliji opisao bi položaj, pa bi idući podredak u aplikaciji
   morao pogoditi istu mjeru napamet.
   Bez vlastite gornje crte: skupina se tako čita kao jedan blok, a ne kao niz
   ravnopravnih redaka. */
.table tbody tr.is-sub > td { color: var(--md-on-surface-variant); border-top: none; }
.table tbody tr.is-sub > td:first-child { padding-left: 2.25rem; }

/* Ćelija s radnjama uz redak — ikone se pakiraju UZ DESNI RUB.
   🔑 Stupac je širok koliko NAJBOGATIJI redak, pa je uz lijevo poravnanje olovka
   u siromašnijem retku visjela po sredini, u liniji s tuđom ikonom dokumenta.
   Desno poravnanje drži zadnju ikonu uvijek na istom mjestu, a one koje redak
   ima viška rastu ulijevo ispred nje. */
.table td.cell-actions, .table th.cell-actions { width: 1%; white-space: nowrap; text-align: right; }
/* .8rem vodoravno, ne .9rem: u širokim tablicama s mnogo stupaca razlika od
   0,1rem po strani znači desetke piksela po retku — dovoljno da zadnji stupac
   stane u okvir umjesto da se reže. */
.table.rows-lg th, .table.rows-lg td { padding: 1rem .8rem; }

/* ── Ćelija podijeljena po visini ────────────────────────────────────────
   Jedan redak tablice koji u istom stupcu nosi VIŠE zapisa: ugovor je jedan
   redak, a izvještaja na njemu zna biti tri — svaki sa svojim rokom, datumom i
   arhivom (§7.6.15). Zapisi se slažu jedan ispod drugog i razdvajaju crtom, pa
   ćelija izgleda kao da je podijeljena po visini.

   🔑 VISINA STAVKE JE FIKSNA, i to je uvjet da se stupci poravnaju. Susjedne se
   ćelije dijele svaka za sebe — smjela li bi se stavka razvući preko dva retka,
   treći rok ne bi više stajao uz svoj datum nego uz tuđi. Otuda `nowrap` i
   stalna `line-height`: predug sadržaj razvlači STUPAC, nikad redak.

   ⚠️ Podstava ide na STAVKU, a ćelija ostaje bez nje — inače crta ne doseže do
   rubova stupca, pa se čita kao podcrtan tekst umjesto kao granica zapisa.

   🔑 Poravnanje nosi stupac (`.center`, `.num`), ne ova klasa: `text-align` se
   nasljeđuje na stavke, pa ćelija ne mora znati što je u njoj. */
.table td.cell-split { padding: 0; }
.table td.cell-split > * {
  padding: .55rem .9rem;
  line-height: 1.5rem;
  white-space: nowrap;
  overflow: hidden;
}
.table td.cell-split > * + * { border-top: 1px solid var(--md-outline-variant); }

/* ⚠️ DUGAČKA VRIJEDNOST RAZVLAČI STUPAC, a to se vidi tek u mjerenju. Naziv
   zakona od 72 znaka („Zakon o financijskom poslovanju i računovodstvu
   neprofitnih organizacija") dao je stupac od **444 px** — dvostruko širi od
   ijednog drugog u tablici, pa je gurao sve iza sebe izvan vidnog polja.
   Prelamanje nije rješenje: ono bi razvuklo redak, a visina stavke je ovdje
   uvjet poravnanja. Zato se krati, a puni tekst ostaje u `title` i u izvozu. */
.table td.cell-split.is-clipped > * { max-width: 15rem; text-overflow: ellipsis; }

/* ═══ Izdan izvještaj: zeleno kroz tri razine ════════════════════════════
   Evidencija angažmana (§7.6.15). Ugovor nosi više izvještaja, pa „gotovo"
   nije jedno stanje nego tri, i svako ima svoj nositelj:

     1. jedan izvještaj od više → zeleni se NJEGOVA stavka u podijeljenoj
        ćeliji, ondje gdje mu i stoje rok, datum i arhiva;
     2. svi izdani → zeleni se CIJELI redak, od „RB" nadalje;
     3. dio izdanih → redak OSTAJE neutralan, a mjerilo uz lijevi rub „RB"
        kaže koliki je udio gotov.

   🔑 Zašto djelomičan ugovor ne boji cijeli redak: tada bi „sve gotovo" i „jedno
   od tri gotovo" izgledali isto, a razlika je upravo ono zbog čega se boja i
   uvodi. Puna ploha je zato rezervirana za dovršen posao.

   ⚠️ Mjerilo je UZ RUB, a ne postotak visine cijele ćelije. Ćelija „RB" proteže
   se i kroz retke rata, pa bi njezina trećina kod ugovora s jednom ratom pala
   točno preko prvog izvještaja i tvrdila da je gotov baš on — a gotov zna biti
   drugi. Uska traka se čita kao mjerilo, ne kao karta redaka; koji je izvještaj
   izdan piše u ćelijama desno.

   Traka je `background-image` na samoj ćeliji, ne `::before` — zamrznuti stupci
   su `position: sticky`, pa bi im `position: relative` za sidrenje pseudoelementa
   srušio zamrznutost.

   ⚠️ PLOHA STOJI NA RETKU, A ĆELIJE JE NASLJEĐUJU. Zamrznuti stupci moraju biti
   neprozirni (inače kroz njih klizi ostatak tablice), pa im `.keep-left-4` daje
   `background: var(--md-surface)` — kraticom, koja usput briše i `background-image`.
   Zato se boja ne piše svakoj ćeliji posebno nego se jednim pravilom veće
   težine vraća na `inherit`: tako isticanje pod mišem ostaje JEDNA izmjena na
   retku, a ne pet usklađenih pravila. */
.table tr.rep-done { background-color: var(--row-done); }
.table-wrap.keep-left-4 .table tr.rep-done > td { background-color: inherit; }

/* Isticanje pod mišem ne smije progutati oznaku gotovog — ploha se produbi,
   a ne zamijeni drugom bojom. */
.table tbody tr.rep-done:hover {
  background-color: color-mix(in srgb, var(--row-done) 90%, var(--md-on-surface) 10%);
}

.table td.cell-split > .rep-done { background: var(--row-done); }

/* Mjerilo napretka uz lijevi rub „RB". Specifičnost je namjerno dignuta na
   razinu `.keep-left-4` pravila — bez toga njegova kratica `background` pojede
   ovu sliku, i traka se jednostavno ne pojavi. */
.table-wrap.keep-left-4 .table td.rep-part,
.table td.rep-part {
  background-image: linear-gradient(to bottom,
      var(--md-success-light) 0 var(--rep-share, 0%),
      var(--md-outline-variant) var(--rep-share, 0%));
  background-size: 6px 100%;
  background-repeat: no-repeat;
}

/* Ćelija s REČENICOM, a ne s vrijednošću — uvjet plaćanja („jednokratno nakon
   izdavanja izvješća"), ne datum ni iznos.

   ⚠️ U ŠIROKOJ TABLICI TAKVA ĆELIJA OSTANE BEZ ŠIRINE. Automatski raspored
   dijeli prostor po potrebi, a susjedni su stupci `nowrap` — njihova je potreba
   apsolutna, pa se jedini stupac koji se SMIJE prelomiti stisne na širinu
   najdulje riječi. *Izmjereno:* 96 px i svih 15 uvjeta prelomljeno, redak 90 px.

   🔑 16 rem NIJE OKRUGAO BROJ NEGO PRAG. Mjereno po koracima: 11 rem → 10 od 15
   prelomljenih, 13 rem → 8, 15 rem → 8, **16 rem → 0**. Ispod toga najdulji
   uvjet ide u dva retka; iznad se ne dobiva ništa. Tablica naraste 4235 → 4395
   px, dakle 4 % na nešto što se ionako vodoravno pomiče.

   🔑 `min-width`, a ne `width`: ne propisuje se širina nego donja granica ispod
   koje raspored ne smije stisnuti — stupac i dalje raste ako ima mjesta, a
   dulji uvjet od ovih se slobodno prelomi. Prag nije obećanje. */
.table th.cell-prose, .table td.cell-prose { min-width: 16rem; }

/* Ćelija je containing block za ono što lebdi nad njom (brojač znakova).
   Polja u redovima tablice nose `.input.sm` — gustoća je modifikator. */
.table td { position: relative; }

/* ── Zaglavlje po kojem se sortira ───────────────────────────────────────
   Gumb, a ne `onclick` na ćeliji: sortiranje je radnja, pa mora raditi i
   tipkovnicom i javiti se čitaču ekrana. Nasljeđuje font i boju zaglavlja,
   pa se vidi kao naslov stupca, a ne kao kontrola nabačena na njega.

   Poravnanje NE ide na gumb: `th` ga već ima iz `.center` / `.num`, a
   `inherit` ga prenosi — tako jedan modifikator i dalje pomiče i naslov i
   strelicu, bez drugog mjesta koje bi ga trebalo znati.

   🔑 RAZMAK OSTAJE NA ĆELIJI, a gumb ima svoj. Zvuči kao dvostruko, i jest —
   ali samo bi lijevo poravnat stupac time bio pomaknut, jer se ondje razmaci
   zbrajaju s iste strane s koje počinje tekst. Zato se skida točno taj jedan
   razmak (v. pravilo ispod), a ne razmak cijele ćelije: ćelija ga dijeli sa
   svim ostalim zaglavljima, pa bi joj ga dirati značilo pomaknuti i njih. */
.sort-btn {
  display: block; width: 100%; padding: .8rem .9rem;
  border: none; background: none;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  text-align: inherit;                  /* poravnanje ostaje na `th` */
  cursor: pointer;
  transition: color var(--dur-short) var(--ease-standard);
}
.table.rows-lg th.sortable > .sort-btn { padding: 1rem .9rem; }

/* 🔑 SAMO LIJEVO PORAVNAT STUPAC gubi lijevi razmak gumba — inače naslov stoji
   14 px desno od sadržaja ispod sebe, jer se razmak ćelije i razmak gumba
   zbrajaju s iste strane. Desno poravnatom stupcu to se ne događa: ondje tekst
   počinje s druge strane, pa lijevi razmak nikoga ne pomiče.

   Uvjet je `:not(.num):not(.center)`, a ne klasa `.left`: lijevo je ZADANO
   poravnanje, pa ga većina zaglavlja ne označava ničim. Stoji poslije
   `.rows-lg` pravila, koje razmak postavlja kraticom i inače bi ga vratilo. */
.table th.sortable:not(.num):not(.center) > .sort-btn { padding-left: 0; }

.sort-btn:hover { color: var(--md-on-surface); }

/* 🔑 NATPIS NOSI PORAVNANJE, STRELICA VISI UZ NJEGA.
   Dok je strelica bila član retka, ulazila je u širinu i odgurivala natpis —
   centrirani naslov time nije bio centriran nad svojim stupcem, nego pomaknut
   za pola širine strelice i razmaka. Izvan toka ne sudjeluje u rasporedu, pa
   naslov stoji točno ondje gdje ga `text-align` stavlja. Usput nestaje i
   potreba da stanja budu jednako široka — njihova širina više nikoga ne pomiče.

   Sidro je omotač natpisa, ne ćelija: strelica prati KRAJ TEKSTA, a ne rub
   stupca, pa stoji na istoj strani u svakom stupcu bez obzira na poravnanje.
   Uz desno poravnan stupac time zalazi u razmak ćelije — i to je u redu, jer
   se ondje ionako ništa ne crta, a strelica koja bi jednom stupcu skočila na
   drugu stranu čitala bi se kao da znači nešto drugo. */
.sort-label { position: relative; }
.sort-label::after {
  position: absolute;
  left: 100%; top: 50%;
  transform: translateY(-50%);
  margin-left: .32rem;
}

/* 🔑 STRELICA NE DOLAZI IZ `aria-sort`, i to je odluka.
   `aria-sort` nosi istinu o podacima (uzlazno/silazno) jer je to ono što
   izgovori čitač ekrana. Strelica nosi drugo pitanje: stoji li stupac u svom
   PRIRODNOM smjeru ili je okrenut. Razlog je što „A" jest najmanja vrijednost,
   ali je u glavi početak niza — pa bi prvi klik na naziv dao ▲, a prvi klik na
   brojku ▼, iako je u oba slučaja riječ o istoj radnji. Ovako je ▼ uvijek
   „prirodno", ▲ uvijek „okrenuto".

   ⚠️ Cijena je svjesna: ▼ na nazivu znači A→Z, što je obrnuto od Excela.
   Prihvaćeno jer se ovdje uči jedno pravilo za cijelu tablicu, umjesto da se
   za svaki stupac pamti je li tekstualni ili brojčani.

   NESORTIRAN stupac nosi blijedi PAR trokuta, i to nije ukras nego jedino što
   odaje da se po stupcu uopće može sortirati: kad oznaku ima samo aktivni
   stupac, onaj tko za tu mogućnost ne zna nema razloga ni kliknuti. Par ne
   tvrdi ništa o stanju — čita se kao „može u oba smjera" — pa se ne može
   zamijeniti s trenutnim poretkom.

   ⚠️ To su ISTI trokuti kao u aktivnom stanju, ne linijska dvosmjerna strelica
   (`⇅`): ona je pri ovoj veličini pretanka i jednostavno se ne vidi. Razliku
   nosi PROZIRNOST i boja, a ne drugi oblik.

   Rezervacija širine više ne treba — strelica je izvan toka (v. `.sort-label`),
   pa ni ne može pomaknuti natpis pri promjeni stanja.

   Strelica koja se pojavi tek na hover bila bi urednija, ali rješava krivi
   problem — nevidljiva je onome tko ne zna da treba prijeći mišem, a na dodiru
   je nema uopće. */
.sort-label::after {
  content: '▲▼';
  font-size: .78em; line-height: 1;
  letter-spacing: -.14em;               /* stisnuti par u jedan znak */
  color: var(--md-on-surface-variant);
  opacity: .5;
  transition: opacity var(--dur-short) var(--ease-standard);
}
.sort-btn:hover .sort-label::after { opacity: .85; }

/* Aktivno stanje mijenja SAMO težinu i broj trokuta — veličina je ista, pa
   zamjena para jednim znakom ne izgleda kao promjena tipografije. */
th[data-order] > .sort-btn > .sort-label::after {
  letter-spacing: normal;
  opacity: 1;
  color: var(--md-primary);
}
th[data-order="natural"]  > .sort-btn > .sort-label::after { content: '▼'; }
th[data-order="reversed"] > .sort-btn > .sort-label::after { content: '▲'; }
th[data-order] > .sort-btn { color: var(--md-on-surface); }

/* ═══ Lista provjera ═════════════════════════════════════════════════════ */

.checklist { list-style: none; margin: 0; padding: 0; }
.checklist li {
  display: flex; gap: .85rem; align-items: flex-start;
  padding: .8rem 0;
  border-bottom: 1px solid var(--md-outline-variant);
}
.checklist li:last-child { border-bottom: none; }
.checklist .mark {
  flex: 0 0 24px; height: 24px;
  border-radius: var(--shape-full);
  display: grid; place-items: center;
  font-size: 1rem;
  background: var(--md-surface-container-high);
  color: var(--md-on-surface-variant);
}
.checklist .mark.ok   { background: var(--md-success-container); color: var(--md-success); }
.checklist .mark.warn { background: var(--md-warning-container); color: var(--md-warning); }
.checklist .mark.fail { background: var(--md-error-container);   color: var(--md-error); }
.checklist .body { flex: 1; min-width: 0; }
.checklist .body .label-t { font: var(--type-title-sm); text-transform: none; letter-spacing: 0; color: var(--md-on-surface); }
.checklist .body .detail  { font: var(--type-body-sm); color: var(--md-on-surface-variant); }
.checklist .body .impact  { font: var(--type-body-sm); color: var(--md-on-surface-variant); margin-top: .2rem; }

/* ═══ Navigacija ═════════════════════════════════════════════════════════ */

/* 🔑 PRELAMANJE NOSI SADRŽAJ, NE PRAG U PIKSELIMA. Koliko traka traži ovisi o
   uključenim modulima i roli (adminova navigacija traži 1145 px, zaposlenikova
   trećinu toga), pa bi svaki `max-width` bio točan samo za jednu kombinaciju —
   a kriv za sve ostale: ili bi se traka prelamala bez potrebe, ili bi stranica
   dobila vodoravni pomak (*izmjereno na 1380 px*). Ovako navigacija sama siđe u
   svoj red čim ne stane uz logo, i ondje se po potrebi prelomi u dva reda.

   ⚠️ Prelamanje, a NE `overflow-x: auto`: pomična traka postaje spremnik koji
   reže sve što iz nje viri — a iz nje vire podizbornici (`.submenu`). */
.topbar {
  display: flex; align-items: center; gap: 1.5rem;
  flex-wrap: wrap;
  padding: .75rem 1.5rem;
  background: var(--md-surface-container-lowest);
  border-bottom: 1px solid var(--md-outline-variant);
  position: sticky; top: 0; z-index: 20;
}
.topbar .brand {
  display: flex; align-items: center; gap: .6rem;
  font: var(--type-title-md);
  color: var(--md-on-surface);
  /* ⚠️ `flex: none` NIJE ukras. Dok je ovdje stajao naziv firme, pritisak duge
     navigacije upijao je tekst prelamanjem u dva retka. Logo se ne prelama nego
     se steže: *izmjereno* — na 1280 px pao je na **30×6 px**, a na 1100 px na
     nulu, i to bez ijedne greške. Sada drži svoju veličinu, a prostor ustupa
     navigacija (v. prag niže). */
  flex: none;
}
/* Logo u traci: širinu zadaje CSS, omjer `viewBox`. */
.topbar .brand .logo-mark { width: 130px; height: auto; display: block; }

/* Kvadratić s ikonom uz naziv u traci. U aplikaciji firme ga više nema — ondje
   traku drži logo, a firma se potpisuje u hero sekciji (`.company-badge`) — ali
   HOST PANEL i dalje ovako predstavlja sebe (`public/host/_layout.php`), pa
   pravilo ostaje. Obrisano, ikona ondje ostaje bez svoje plohe. */
.topbar .brand .logo {
  width: 32px; height: 32px;
  border-radius: var(--shape-sm);
  background: var(--md-primary-container);
  color: var(--md-on-primary-container);
  display: grid; place-items: center;
}
.topbar .brand .sub { font: var(--type-body-sm); color: var(--md-on-surface-variant); }

/* ── Oznaka firme ─────────────────────────────────────────────────────────
   Kvadratić s ikonom i naziv. Pojam je imenovan, pa može stajati gdje treba —
   danas u kutu hero sekcije, a mjere su iste bez obzira na domaćina.
   Naziv je namjerno sitan i u jednom retku: ovo je potpis ekrana, a ne naslov;
   prelomljen bi se natjecao s pozdravom iznad. */
.company-badge {
  display: inline-flex; align-items: center; gap: .5rem;
  color: var(--md-on-surface-variant);
}
.company-badge:hover { text-decoration: none; }
.company-badge .logo {
  width: 28px; height: 28px;
  border-radius: var(--shape-sm);
  background: var(--md-primary-container);
  color: var(--md-on-primary-container);
  display: grid; place-items: center;
}
.company-badge .logo .icon { font-size: 1.075rem; }
.company-badge .name { font-size: .7rem; white-space: nowrap; }
a.company-badge:hover .logo { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }

.topbar nav { display: flex; flex-wrap: wrap; gap: .25rem; margin-left: auto; align-items: center; }
.topbar nav a {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .5rem .9rem;
  border-radius: var(--shape-full);
  font: var(--type-label-lg);
  color: var(--md-on-surface-variant);
  /* Dvorječne stavke („Radna knjiga") inače se prelome na dva reda čim traka
     postane tijesna, pa jedna stavka naraste i razbije ravnu liniju. */
  white-space: nowrap;
  transition: background var(--dur-short) var(--ease-standard);
}
.topbar nav a:hover { background: var(--md-surface-container); text-decoration: none; }
.topbar nav a.active { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }
.topbar nav a .icon { font-size: 1.125rem; }

/* ── Skupina s podizbornikom ──────────────────────────────────────────────
   Korijen skupine OSTAJE poveznica na svoju stranicu: podizbornik je dodatak,
   ne zamjena za klik. Zato se otvara na prijelaz mišem i na tipkovnički fokus
   (`:focus-within`) — bez JavaScripta, pa radi i dok se skripta učitava.

   `visibility` uz `opacity`: samo prozirnost ostavlja nevidljive poveznice u
   tabulatorskom redu i pod mišem. */
.topbar .nav-group { position: relative; display: flex; }
.topbar .nav-group > a .nav-caret {
  font-size: 1rem;
  margin-inline-start: -.2rem;
  color: var(--md-on-surface-variant);
  transition: transform var(--dur-short) var(--ease-standard);
}
.topbar .nav-group > a.active .nav-caret { color: inherit; }
.topbar .nav-group:hover > a .nav-caret,
.topbar .nav-group:focus-within > a .nav-caret { transform: rotate(180deg); }

.topbar .submenu {
  position: absolute; top: 100%; inset-inline-start: 0;
  min-width: 100%; width: max-content;
  display: flex; flex-direction: column; gap: .15rem;
  padding: .35rem;
  background: var(--md-surface-container-lowest);
  border: 1px solid var(--md-outline-variant);
  border-radius: var(--shape-md);
  box-shadow: var(--elev-2);
  z-index: 30;
  opacity: 0; visibility: hidden; transform: translateY(-.25rem);
  transition: opacity var(--dur-short) var(--ease-standard),
              transform var(--dur-short) var(--ease-standard),
              visibility var(--dur-short);
}
.topbar .nav-group:hover .submenu,
.topbar .nav-group:focus-within .submenu {
  opacity: 1; visibility: visible; transform: none;
}
.topbar .submenu a { padding: .45rem .75rem; border-radius: var(--shape-sm); }

@media (max-width: 860px) {
  .topbar { gap: .75rem; padding: .6rem .9rem; flex-wrap: wrap; }
  .topbar nav { width: 100%; margin-left: 0; overflow-x: auto; }

  /* Na dodir nema prijelaza mišem, a klik na korijen odvodi na stranicu — pa bi
     podizbornik ostao nedohvatljiv. Zato se ovdje raspusti: `display: contents`
     vraća i korijen i djecu u isti vodoravni red koji se ionako već pomiče. */
  .topbar .nav-group,
  .topbar .submenu { display: contents; }
  .topbar .submenu { opacity: 1; visibility: visible; transform: none; }
  .topbar .nav-group > a .nav-caret { display: none; }
}

/* ═══ Prazno stanje ══════════════════════════════════════════════════════ */

.empty { text-align: center; padding: 3.5rem 1.5rem; }
/* Velika prigušena ilustracija praznog stanja — imenovan pojam, pa se ne može
   zabuniti s bilo kojom drugom ikonom koja se nađe unutar `.empty`. */
.empty-art { font-size: 3rem; color: var(--md-outline); margin: 0 auto .75rem; }
.empty h3 { margin-bottom: .35rem; }
.empty p  { color: var(--md-on-surface-variant); max-width: 42ch; margin: 0 auto 1.5rem; }

/* ═══ Hero s ukrasnim oblicima ═══════════════════════════════════════════
   Mekani pastelni "blobovi" u pozadini — ukras kakav si tražio.
   Čisti CSS, bez slika.                                                    */

/* 🔑 VISINA JE MJERA EKRANA, NE ZBROJ REDAKA. Izjava dana je ispod pozdrava
   dodavala svoj redak, pa je hero rastao i sve ispod njega se spuštalo —
   uključeno/isključeno micalo je cijelu stranicu. Mjesta je unutra bilo i
   prije; trošio ga je padding. Sada hero drži svoju mjeru, a tekst se u njoj
   centrira po visini: s izjavom se cijeli blok podigne umjesto da hero
   naraste. Dulji pozdrav (uski ekran, više redaka) i dalje može preko —
   `min-height` je najmanja, ne jedina dopuštena mjera. */
.hero {
  position: relative;
  overflow: hidden;
  border-radius: var(--shape-xl);
  min-height: 10rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 1.5rem 2rem;
  background: var(--md-surface-container-low);
  isolation: isolate;
}
.hero::before, .hero::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  filter: blur(48px);
  opacity: .75;
  z-index: -1;
}
.hero::before {
  width: 340px; height: 340px;
  background: var(--md-primary-container);
  top: -140px; right: -80px;
}
.hero::after {
  width: 280px; height: 280px;
  background: var(--md-tertiary-container);
  bottom: -160px; left: -60px;
}
.hero > * { position: relative; }
.hero .greeting { font: var(--type-display-sm); letter-spacing: -.02em; margin: 0 0 .35rem; }
.hero .sub { color: var(--md-on-surface-variant); margin: 0; }

/* Firma potpisuje ekran iz donjeg desnog kuta.
   ⚠️ Odmaknuto od ruba zbog ZAOBLJENJA (`--shape-xl`, 28 px): oznaka priljubljena
   uz kut ulazi u luk i izgleda kao da strši van kartice. Razmaci su veći od
   polumjera koji luk „pojede" po dijagonali (~8 px), pa je kut čist i kad se
   naziv produži. */
.hero .company-badge { position: absolute; right: 1.5rem; bottom: .85rem; }

@media (max-width: 640px) {
  /* Na uskom ekranu pozdrav se prelama preko dva-tri retka i stigne do dna, pa
     bi mu apsolutno pozicionirana oznaka legla preko teksta. Ondje se vraća u
     tok, ispod pozdrava. */
  .hero .company-badge { position: static; margin-top: 1.25rem; }
}

/* ═══ Prijava ════════════════════════════════════════════════════════════ */

.auth-wrap {
  min-height: 100vh;
  display: grid;
  place-items: center;
  /* ⚠️ BEZ OVOGA RAZMAK DO PROZORA NIJE ONAJ KOJI PIŠE. Mreža ima dva retka
     (logo i prozor), a `min-height: 100vh` ostavlja višak visine koji se bez
     `align-content` razdijeli MEĐU RETKE: svaki dobije pola zaslona, stavka se
     centrira u svojoj polovici, i logo odleti visoko iznad prozora bez obzira
     na marginu. Ovako retci stoje skupa, pa 45 px doista znači 45 px. */
  align-content: center;
  padding: 2rem 1rem;
  position: relative;
  overflow: hidden;
  background: var(--md-surface);
}
.auth-wrap::before, .auth-wrap::after {
  content: ''; position: absolute; border-radius: 50%; filter: blur(70px); opacity: .6;
}
.auth-wrap::before { width: 420px; height: 420px; background: var(--md-primary-container);  top: -120px; left: -120px; }
.auth-wrap::after  { width: 380px; height: 380px; background: var(--md-tertiary-container); bottom: -140px; right: -100px; }
.auth-card { width: 100%; max-width: 420px; position: relative; z-index: 1; box-shadow: var(--elev-2); border-color: transparent; }

/* Logo iznad prozora za prijavu.
   🔑 ISTA ŠIRINA KAO PROZOR + njegov unutarnji razmak (`.card` ima 1,5 rem), pa
   se lijevi rub logotipa poklapa sa sadržajem u prozoru, a ne s njegovim rubom.
   Razmak do prozora je 45 px — dovoljno da logo stoji sam za sebe, a da se ne
   odvoji u zaseban element stranice. */
.auth-logo {
  width: 100%; max-width: 420px;
  /* Razmak kartice (1,5 rem) PLUS njezin rub od 1 px — `.card` ga ima i kad je
     proziran, pa sadržaj u prozoru počinje jedan piksel desnije. Bez toga logo
     stoji za taj piksel ulijevo, što se na ravnoj okomici vidi. */
  padding-inline-start: calc(1.5rem + 1px);
  margin-bottom: 45px;
  position: relative; z-index: 1;
}
.auth-logo .logo-mark { width: 203px; height: auto; display: block; }

/* Naziv firme u prozoru: manji od zadanog naslova, jer glavnu riječ ondje sada
   nosi logo iznad. Dugo ime inače prelomi prozor u tri retka. */
.auth-card h1 { font-size: 1.6rem; }
.auth-card .brand-mark {
  width: 52px; height: 52px;
  border-radius: var(--shape-md);
  background: var(--md-primary-container);
  color: var(--md-on-primary-container);
  display: grid; place-items: center;
  margin-bottom: 1.25rem;
}

/* ═══ Koračnik ═══════════════════════════════════════════════════════════ */

.steps { display: flex; gap: .5rem; margin-bottom: 2rem; }
.step {
  flex: 1;
  height: 4px;
  border-radius: var(--shape-full);
  background: var(--md-surface-container-high);
}
.step.done   { background: var(--md-primary); }
.step.active { background: var(--md-primary); opacity: .55; }

/* ═══ Picker — search-as-you-type ════════════════════════════════════════
   Jedan izgled za dvije uporabe, jer je pojam isti („popis koji se otvara
   ispod polja dok se tipka"): odabir projekta u radnoj knjizi, gdje je unos
   VEZAN uz postojeći zapis, i prijedlog grada na kartici klijenta, gdje je
   unos slobodan a popis samo pomaže da se isti grad piše isto. Razlika je u
   ponašanju (JS), ne u izgledu — pa ne traži drugi blok.                    */

.picker { position: relative; }

/* Polje pickera JEST `.input` — u markupu nosi obje klase, pa izgled dolazi
   iz jedne definicije, a `.picker-input` ostaje samo hvatište za JS.
   Unutar `.field` klasa nije potrebna: polje je već obuhvaćeno njegovim
   izgledom. */

.picker-list {
  position: absolute;
  top: calc(100% + 4px);
  left: 0; right: 0;
  z-index: 40;
  /* Napomena: roditelj NE SMIJE imati overflow:hidden — inače reže ovu listu. */
  max-height: 280px;
  overflow-y: auto;
  background: var(--md-surface-container-lowest);
  border: 1px solid var(--md-outline-variant);
  border-radius: var(--shape-md);
  box-shadow: var(--elev-3);
  padding: .35rem;
}
.picker-item {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  padding: .55rem .7rem;
  border-radius: var(--shape-sm);
  cursor: pointer;
  font: var(--type-body-md);
}
/* Kad ispod nema mjesta (zadnji redovi tablice) — otvori prema gore. */
.picker-list.above { top: auto; bottom: calc(100% + 4px); }

/* Napomena o <input type="date">: panel kalendara NIJE dio stranice —
   preglednik ga crta u svom sloju, pa se „danas", odabrani dan i obrubi
   ne mogu stilizirati. Nema pseudo-elementa za njih. Polje je namjerno
   ostavljeno u zadanom izgledu. */

.picker-item:hover, .picker-item.active { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }
.picker-item mark { background: var(--md-primary-container); color: var(--md-on-primary-container); border-radius: 3px; padding: 0 1px; }
.picker-empty { padding: .8rem .7rem; font: var(--type-body-sm); color: var(--md-on-surface-variant); }

/* Redak koji je upravo spremljen kratko zasvijetli — tiha potvrda. */
@keyframes row-saved {
  0%   { background: var(--md-success-container); }
  100% { background: transparent; }
}
tr.saved > td { animation: row-saved 900ms var(--ease-decelerate); }

/* ═══ Pogođeni pojam u živoj pretrazi ════════════════════════════════════
   Filtar kaže KOJI zapisi odgovaraju; boja kaže GDJE u zapisu. Na zapisu od
   desetak redaka se bez toga i dalje traži očima.

   🔑 Boja NE ULAZI U DOM. `<mark>` bi tražio prepisivanje sadržaja, a to na
   niti komunikacije ruši dvije stvari koje rade upravo zato što ih nitko ne
   dira: `content-visibility: auto` zaboravi zapamćenu visinu preskočenog
   zapisa (traka za skrolanje poskoči), a rezervirana visina i namještanje
   vrha rezultata računaju nad visinama koje bi se tad mijenjale pod prstima.
   `::highlight()` crta preko POSTOJEĆEG teksta — bez elementa, bez okvira,
   bez rasporeda.

   Zato su dopuštene samo boje i ukras teksta (ne radijus, ne razmak, za
   razliku od `<mark>` u pickeru) — a to je ovdje dovoljno: naglasak nosi
   ploha, ne oblik. Preglednik koji API ne zna nema boju; filtar radi isto. */

::highlight(crm-hit) {
  background-color: var(--md-primary-container);
  color: var(--md-on-primary-container);
}

/* ═══ Pulsiranje — skretanje pažnje na jednu radnju ══════════════════════ */

@keyframes pulse-ring {
  0%   { box-shadow: 0 0 0 0   color-mix(in srgb, var(--md-primary) 45%, transparent); }
  70%  { box-shadow: 0 0 0 12px color-mix(in srgb, var(--md-primary) 0%,  transparent); }
  100% { box-shadow: 0 0 0 0   color-mix(in srgb, var(--md-primary) 0%,  transparent); }
}

.pulse {
  animation: pulse-ring 2s var(--ease-decelerate) infinite;
}

/* Poštuj sistemsku postavku — animacija ne smije smetati onima koji je gase. */
@media (prefers-reduced-motion: reduce) {
  .pulse { animation: none; outline: 2px solid var(--md-primary); outline-offset: 2px; }
}

/* ═══ Dijalog ════════════════════════════════════════════════════════════ */

.scrim {
  position: fixed;
  inset: 0;
  background: color-mix(in srgb, var(--md-scrim) 40%, transparent);
  display: grid;
  place-items: center;
  padding: 1.5rem;
  z-index: 100;
  animation: fade-in var(--dur-short) var(--ease-standard);
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes dialog-in {
  from { opacity: 0; transform: translateY(12px) scale(.97); }
  to   { opacity: 1; transform: none; }
}

.dialog {
  background: var(--md-surface-container-high);
  color: var(--md-on-surface);
  border-radius: var(--shape-xl);
  padding: 1.75rem;
  max-width: 420px;
  width: 100%;
  box-shadow: var(--elev-3);
  animation: dialog-in var(--dur-medium) var(--ease-emphasized);
}
.dialog .dialog-icon {
  width: 48px; height: 48px;
  border-radius: var(--shape-full);
  display: grid; place-items: center;
  background: var(--md-warning-container);
  color: var(--md-warning);
  margin: 0 auto 1rem;
}
.dialog h3 { text-align: center; margin: 0 0 .5rem; }
.dialog p  { text-align: center; color: var(--md-on-surface-variant); margin: 0 0 1.5rem; font: var(--type-body-md); }
.dialog .dialog-actions { display: flex; gap: .75rem; justify-content: flex-end; flex-wrap: wrap; }
.dialog .dialog-actions .btn { flex: 1 1 auto; justify-content: center; }
/* Za dijalog s poljem za unos — pitanje stane u 420px, tekst ne. */
.dialog.wide { max-width: 560px; }
.dialog.wide h3 { text-align: left; }

/* ═══ Sitna radnja uz rub polja ══════════════════════════════════════════
   Gumb koji „nešto upiše" u polje pripada polju, a ne traci gumba negdje sa
   strane — inače nije jasno u koje polje piše. Lijevi rub je slobodan jer su
   iznosi poravnati desno.                                                   */

.with-affix { position: relative; display: block; }
.with-affix > .affix {
  position: absolute;
  left: .2rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--md-outline);
}
.with-affix > .affix:hover { color: var(--md-primary); }
.with-affix > .input { padding-left: 2rem; }

/* Uz DESNI rub — za radnju nad sadržajem polja (kopiranje), gdje lijevi rub
   drži tekst. Sadržaj se tada odmiče desno umjesto lijevo. */
.with-affix.end > .affix { left: auto; right: .2rem; }
.with-affix.end > .input { padding-left: .9rem; padding-right: 2.2rem; }
.icon-btn.copied .icon { color: var(--md-success-light); }

/* ═══ Bilješka u dijalogu ════════════════════════════════════════════════
   Dugačak tekst u uskoj ćeliji je procjep kroz koji se čita pet znakova.
   Ikona otvara dijalog, a BOJA nosi informaciju: siva = prazno, zelena =
   postoji zapis. Tako se po stupcu odmah vidi gdje bilješka ima.            */

/* Zvjezdica isticanja: stanje nosi ISPUNA, ne boja. Font je varijabilan po osi
   `FILL` (v. `bin/icons.php`), pa je označena zvjezdica puna, a neoznačena
   crtana — u istoj boji (`--md-primary`), da razlika bude oblik, a ne šarenilo.
   Varijabla, ne zapisana vrijednost: boja prati temu i buduće brandiranje. */
.icon-btn.star .icon { color: var(--md-primary); }
.icon-btn.star.on .icon { font-variation-settings: 'FILL' 1; }

/* ═══ Podizbornik s skokovima ════════════════════════════════════════════
   Pilule u donjem desnom kutu — ondje kursor ionako miruje, pa je put do njih
   najkraći. Lebdi nad sadržajem, s podlogom da ostane čitljiv preko tablica.  */

.jumpnav {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 30;
  /* Jedan STUPAC uz desni rub; sve pilule su jednake širine, pa se cilja
     uvijek u istu okomicu — kursor se pomiče samo gore-dolje. */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
}
/* Bez okvira oko skupine — pilule same nose podlogu, pa je kutija bila samo
   dodatna ploha. Jednake širine, sadržaj centriran: stupac čita kao izbornik,
   a ne kao niz različitih oblika. */
.jumpnav .chip {
  width: 100%;
  justify-content: center;
  background: var(--md-surface-container-lowest);
}
.jumpnav .chip.selected {
  background: var(--md-secondary-container);
  color: var(--md-on-secondary-container);
  border-color: transparent;
}

/* Odmak od ljepljive trake: sekcija se zaustavlja 20 px ISPOD nje, a ne pod
   njom. Visinu trake mjeri JS i upisuje u `--topbar-h`. */
[data-jump-target] { scroll-margin-top: calc(var(--topbar-h, 64px) + 20px); }

/* ═══ Oznaka zapisa na kojem se radi ═════════════════════════════════════
   Kartica klijenta je duga; pri dnu se zaboravi na kojem se klijentu radi, a
   povratak na popis traži skrol do vrha.

   🔑 `position: fixed`, NE ljepljivo zaglavlje. Ljepljivo bi tražilo stanje
   „zalijepljeno" (kojeg u CSS-u nema), promatrača da ga izvede i pomicanje
   zaustavne točke ljepljivom filtru komunikacije koji staje na istoj visini —
   tri pomična dijela u rasporedu koji je već fino namješten. Lebdeća oznaka ne
   dira raspored uopće: izvan je toka, ne pomiče ništa i ne može ništa pokvariti.

   🔑 DOLJE LIJEVO, jer je desni kut zauzet podizbornikom sekcija. Dva lebdeća
   elementa u istom kutu preklapala bi se čim ime bude dugačko.

   Ime je 14 px i skraćuje se s tri točkice — ovo je podsjetnik, ne naslov;
   pun naziv stoji na vrhu kartice, kamo strelica i vodi.

   🔑 POJAVLJUJE SE TEK KAD ZAGLAVLJE ODE S EKRANA. Dok se ime vidi na vrhu,
   oznaka bi ponavljala ono što već piše — a lebdeća kutija koja ništa ne
   dodaje samo pokriva sadržaj. Stanje `is-on` upisuje promatrač nad zaglavljem
   (v. `initClientTag`).

   ⚠️ Skrivena je PROZIRNOŠĆU, ne `display: none` — da se pojava može ublažiti
   prijelazom. Uz to ide `pointer-events: none`, jer nevidljiva kutija ne smije
   presretati klikove po sadržaju ispod sebe.

   Vidljiva je na SVIM širinama. Rizik da na uskom ekranu legne preko sadržaja
   prihvaćen je svjesno: kutija je niska i uz sam rub, a na užem ekranu se
   skrola više — dakle ondje najviše i treba.                                 */
.client-tag {
  position: fixed;
  left: 1rem;
  bottom: 1rem;
  z-index: 25;                      /* iznad sadržaja, ispod podizbornika (30) */
  display: flex; align-items: center; gap: .5rem;
  max-width: min(360px, 60vw);
  padding: .3rem .75rem .3rem .3rem;
  border-radius: var(--shape-lg);
  background: var(--md-surface-container-lowest);
  box-shadow: var(--elev-sticky);
  font: var(--type-body-md);
  font-size: 14px;

  opacity: 0;
  transform: translateY(.5rem);
  pointer-events: none;
  transition: opacity .18s var(--ease-decelerate, ease), transform .18s var(--ease-decelerate, ease);
}
.client-tag.is-on {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
.client-tag .name {
  font-weight: 600;
  /* Dugačak naziv ne smije rasti preko pola ekrana — kutija ostaje oznaka. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Poštuj sistemsku postavku — pojava ostaje, kretnja nestaje. */
@media (prefers-reduced-motion: reduce) {
  .client-tag { transition: opacity .18s ease; transform: none; }
}

/* Podizbornik lebdi UZ sadržaj, ne nad njim. Sadržaj je širok do 1180 px plus
   margine, pa ispod ~1440 px više nema slobodnog pojasa desno i pilule bi
   prekrile karticu. Tada se gase — kraća stranica ionako traži manje skakanja. */
@media (max-width: 1439px) { .jumpnav { display: none; } }

/* ═══ Nit komunikacije ═══════════════════════════════════════════════════
   Popis zapisa na kartici klijenta. Komunikacija je dvosmjerna, pa STRANU
   nosi položaj: klijent lijevo, mi desno — isto što svatko već zna iz
   poruka, pa se ne mora objašnjavati.

   🔑 NAMJERNO NIJE „bubble". Zaobljeni oblačići čitaju se kao ćaskanje, a ovo
   je poslovna evidencija: zapis nosi datum, vrstu, podsjetnik i autora. Zato
   umjeren radijus, ravna ploha i tanka traka sa strane — trag razgovora, ne
   razgovor. Traka usto drži stranu jasnom i kad je zapis toliko dugačak da
   zauzme punu širinu.                                                       */

/* ⚠️ NIT NEMA VLASTITI OKVIR KOJI SKROLA, i to je odluka, ne propust.
   Okvir visok 80vh pokriva gotovo cijeli zaslon, pa kotačić radi različito
   ovisno o tome gdje je kursor — a na svom kraju izgleda kao zaglavljen
   scroll. Uz to je tražio JS: vraćanje na vrh pri filtriranju, lanac scrolla,
   mjerenje. Sve to nestaje kad nit skrola sa stranicom, a sekcija je zadnja
   na kartici pa joj duljina nikoga ne gura.

   Cijenu duge stranice preuzima `content-visibility` niže — nativno. */
/* 🔑 REZERVIRANA VISINA POSTOJI ZBOG LJEPLJIVOG FILTRA, ne zbog izgleda.
   Ljepljiv element se smije kretati samo unutar svog roditelja. Kad pretraga
   suzi rezultate, popis se skrati, kartica s njim, i filtru nestane prostora
   ispod — pa „padne" s vrha. I to točno u trenutku kad se koristi.

   Mjera je **afina, a ne proporcionalna**: odbici su fiksni u pikselima, pa
   postotak visine ne radi — `60vh` koji sjedne na prijenosniku na velikom je
   monitoru premalo, jer ondje isti odbici čine manji udio zaslona. Otud
   `100vh` kao osnova, i odbitak u pikselima.

   ⚠️ ODBITAK NIJE KONSTANTA, i to je cijena naučena ugađanjem po monitoru.
   Pun odbitak je zbroj četiri veličine: traka, filtar, njegov razmak do
   popisa i „rep" ispod popisa (podnožje kartice, napomena o odrezanom popisu,
   donji razmak stranice). Filtar mijenja visinu s brojem pilula po osobama i
   s prelamanjem čipova, rep s time je li popis odrezan, traka sa širinom —
   pa je svaki fiksni broj točan samo na jednom monitoru kod jednog klijenta.
   Zato ga JS ZBRAJA IZ IZMJERENIH VRIJEDNOSTI u `--comm-reserve`.

   Pričuvna vrijednost je namjerno IZDAŠNA (samo traka): bez JS-a je bolje
   imati višak praznine nego filtar koji pada — vidi zašto odmah niže.

   ⚠️ REZERVACIJA MORA BITI DOVOLJNA, NE TOČNA — razlika koja je dugo skrivala
   bug. Sama rezervacija radi tako da krajnji scroll padne točno na mjesto
   gdje vrh popisa dođe pod filtar, a to je ravnoteža na šiljku: premala
   otpušta filtar s vrha, prevelika odnese prvi rezultat POD filtar. Točan
   broj postoji, ali vrijedi samo za jednu visinu filtra i jedan rep.
   Zato vrh rezultata postavlja JS (`revealTop`), a ovdje ostaje samo uvjet
   „stranica se može odskrolati dovoljno da filtar ostane zalijepljen". Višak
   je od tada bezopasan: postane praznina pri dnu, ne kvar.

   ⚠️ IDE NA POPIS, NE NA KARTICU — i ta je razlika izmjerena, ne izvedena.
   Rezerviranje visine na kartici ne radi: kartica iznad filtra nosi zaglavlje
   i obrazac za unos (~400px), pa se rezervirani prostor potroši na njih.
   Mjereno u pregledniku: uz `min-height` na kartici, pri krajnjem scrollu
   nakon filtriranja vrh kartice ostaje na −19px, a filtar na 387px — dakle u
   svom prirodnom položaju, nezalijepljen.

   Na popisu se visina zaglavlja i obrasca KRATI iz jednadžbe: popis je ispod
   filtra, pa sve što je iznad njega pomiče oboje jednako.

   Uvjet „ima li ijedan zapis" nosi POSTOJANJE elementa, a ne `:has()`: zapisi
   se pri filtriranju skrivaju (`hidden`), ne uklanjaju, pa bi `:has(.comm-item)`
   bio istinit uvijek. `.comm-list` se pak ne iscrtava kad zapisa nema.

   Cijena je praznina kod klijenta s jednim ili dva zapisa. Prihvaćena
   svjesno: mirno sučelje vrijedi više od ušteđenog prostora. */
.comm-list {
  display: flex; flex-direction: column; gap: .6rem;

  /* 🔑 GASI SIDRENJE SCROLLA — i to je drugi, potpuno odvojen uzrok istog
     kvara, koji se vidi tek mjerenjem.

     Preglednik sam po sebi pokušava zadržati zatečeni element na istom mjestu
     kad se sadržaj promijeni. Pri filtriranju se skrije petnaest redaka, pa
     preglednik odskrola stranicu prema gore da bi preostali redak ostao gdje
     je bio — i time odnese filtar sa zalijepljenog položaja.

     Mjereno: uz upit koji ostavi dva rezultata scroll padne s 3230 na 2697,
     iako je krajnji scroll i dalje 3230. Nitko ga nije skratio; preglednik ga
     je POMAKNUO. Zato ovdje sidrenje smeta umjesto da pomaže: kod filtriranja
     se ne želi zadržati redak, nego pogled na filtar i vrh rezultata. */
  overflow-anchor: none;
}

/* Rezervirana visina je OPT-IN, jer postoji samo zbog ljepljivog filtra iznad
   niti. Nit na kartici osobe filtra nema, pa bi je ista mjera razvukla preko
   cijelog zaslona bez ijednog razloga — praznina koja ništa ne drži.
   Uz klasu se čita i uvjet: rezervacija ide ondje gdje se nešto lijepi. */
.comm-list.reserved {
  min-height: calc(100vh - var(--comm-reserve, var(--topbar-h, 64px)));
}

.comm-item {
  /* Puna dodijeljena širina, poravnanje ide MARGINOM — ne `align-self`.
     Razlog je `content-visibility` niže: element kojemu širinu određuje
     sadržaj pod ograničenjem veličine nema iz čega je izračunati, pa bi se
     preskočeni zapisi stisnuli i razmicali stranicu pri skrolanju. Ovako je
     širina uvijek poznata iz roditelja. */
  max-width: 82%;
  min-width: 0;
  margin-inline-end: auto;
  padding: .55rem .75rem;
  border: 1px solid var(--md-outline-variant);
  border-radius: var(--shape-sm);
  border-inline-start: 3px solid var(--md-outline-variant);
  background: var(--comm-client-bg);

  /* 🔑 Nativna virtualizacija: zapis izvan zaslona preglednik ne raspoređuje
     i ne crta. Ovo je razlog zašto duga nit ne treba ni okvir koji skrola ni
     JS koji reciklira retke — posao koji se ne radi je najjeftiniji posao.
     `contain-intrinsic-size` drži procijenjenu visinu dok je preskočen, pa
     traka za skrolanje ne poskakuje; `auto` pamti stvarnu visinu jednom
     iscrtanog zapisa. Preglednik koji ovo ne zna jednostavno ga ignorira. */
  content-visibility: auto;
  contain-intrinsic-size: auto 96px;
}

/* Naša strana: desno, druga ploha i traka u naglasnoj boji. Traka mijenja
   stranu s porukom — inače bi obje strane izgledale kao ista kolona. */
.comm-item.ours {
  margin-inline-start: auto;
  margin-inline-end: 0;
  background: var(--md-surface-container);
  border-inline-start: 1px solid var(--md-outline-variant);
  border-inline-end: 3px solid var(--md-primary);
}

.comm-meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: .4rem;
  margin-bottom: .25rem;
}
.comm-meta .icon { color: var(--md-on-surface-variant); }

/* Tekst se PRELAMA i kad je jedna riječ dulja od okvira (URL, broj računa):
   bez toga zapis razvuče karticu i pojavi se vodoravni scroll.              */
.comm-body { white-space: pre-wrap; overflow-wrap: anywhere; }

/* Tri retka su dovoljna da se prepozna o čemu je riječ; ostatak na zahtjev.
   Bez ovoga jedan opširan zapis pojede cijelu visinu niti. */
.comm-body.clamped {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* „Više" postoji SAMO kad tekst vjerojatno ne stane, a tu odluku donosi server
   po duljini teksta (`communication_is_long()`). Preglednik ništa ne mjeri —
   mjerenje visine je ono što je dvaput blokiralo sučelje. */
.comm-more {
  border: none; background: transparent; padding: 0;
  margin-top: .15rem;
  font: var(--type-label-md);
  color: var(--md-primary);
  cursor: pointer;
}
.comm-more:hover { text-decoration: underline; }

.comm-author { font: var(--type-body-sm); color: var(--md-on-surface-variant); margin-top: .25rem; }

/* Filtar je ALAT, ne sadržaj — pa mora imati vlastitu plohu. Bez nje se u
   gustoj kartici gubi među zapisima, obrascem i značkama: polje za pretragu i
   dva niza čipova izgledaju kao još jedan red elemenata umjesto kao kontrola
   nad popisom ispod.

   Hladan ton izvan lavandine ljestvice i blaga sjena rade isto — odvajaju
   traku od ostatka kartice. Radijus i sjena dolaze iz tokena, ne iz brojki.

   LJEPLJIV je jer nit može biti duga nekoliko zaslona: kontrola koja odskroli
   s ekrana traži da se vratiš na vrh da bi je poništio, a to je upravo ono
   zbog čega se filtar prestane koristiti. Zaustavlja se točno pod glavnom
   trakom (`--topbar-h`, mjeri se jer se na užem ekranu prelomi), pa sadržaj
   nestaje pod njim bez procjepa.

   `z-index` je nužan, ne ukras: zapisi nose `content-visibility`, koje stvara
   vlastiti slagaći kontekst, a filtar stoji PRIJE njih u dokumentu — bez
   podizanja bi mu klizili preko. Ispod je trake (20) i podizbornika (30). */
.comm-filter {
  position: sticky;
  top: var(--topbar-h, 64px);
  z-index: 5;
  background: var(--filter-surface);
  padding: 1rem;
  box-shadow: var(--elev-sticky);
  margin-bottom: 1.25rem;
}

/* ═══ Ocjena zvjezdicama ═════════════════════════════════════════════════
   Pet zvjezdica, upisuju subjektivnu ocjenu. U mirovanju su prigušene da ne
   viču s obrasca; na prijelaz mišem preko skupine dobiju punu vidljivost, jer
   tada ih korisnik i gleda. Ocjenu nosi ISPUNA, kao i kod bilješki.          */

.rating { display: inline-flex; gap: .15rem; }
.rating-star {
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
  line-height: 1;
  color: var(--md-primary);
  opacity: .4;
  transition: opacity var(--dur-short) var(--ease-standard);
}
.rating-star .icon { font-size: 2.25rem; }
.rating:hover .rating-star { opacity: 1; }
.rating-star.on { opacity: 1; }
.rating-star.on .icon { font-variation-settings: 'FILL' 1; }

.note { display: inline-flex; }
.note .icon { color: var(--md-outline); }
.note.has-note .icon { color: var(--md-success-light); }

/* ═══ Traka napretka ═════════════════════════════════════════════════════ */

.meter { height: 8px; border-radius: var(--shape-full); background: var(--md-surface-container-high); overflow: hidden; }
.meter > span { display: block; height: 100%; border-radius: inherit; background: var(--md-primary); transition: width var(--dur-medium) var(--ease-standard); }
/* 🔑 BEZ KLASE TRAKA JE U BRAND BOJI, i to je NORMALNO stanje, ne izostanak
   stanja. Boja se pojavljuje tek kad nešto znači: žuta je „na putu prema
   crvenom", crvena je „prijeđeno". Dok je sve u redu, traka nema što reći, pa
   nosi boju teme i ne natječe se s ikonom, značkom i plohom oko sebe.

   `ok` zato NIJE „ispod praga" nego „cilj postignut" — puna zelena traka
   popunjenosti radne knjige. Dvije različite stvari, dvije klase. */
.meter.ok   > span { background: var(--md-success); }

/* Žuta i crvena su ŽIVE (`-strong`), ne tekstualne inačice: ovdje se ništa ne
   čita, pa boja smije biti zasićenija nego što bi kontrast za tekst dopustio.
   Tamna oker `--md-warning` je kao ispuna izgledala kao blato. */
.meter.warn > span { background: var(--md-warning-strong); }
/* Prekoračenje: traka je puna, pa razliku nosi samo boja. */
.meter.fail > span { background: var(--md-error-strong); }

/* ═══ Redak popisa koji je cijeli poveznica ═══════════════════════════════
   Cijeli redak je klik-meta (ime lijevo, brojka desno), pa mora izgledati kao
   redak, a ne kao link u tekstu — otud `color: inherit` i bez podcrte.

   ⚠️ Prije je to stajalo kao inline stil na `<a>`, i time se gubilo i
   `a:hover` iz `base.css`: redak se ničim nije javljao kao klikabilan. Podcrta
   se zato vraća na hover, ali SAMO na imenu — da je na cijelom retku, potegla
   bi se i ispod brojke na drugom kraju, preko praznine između njih.

   🔑 `:hover` STANJE MORA PONOVITI „bez podcrte", inače ga preotme `a:hover`
   iz `base.css`. Specifičnost: `a:hover` je (0,1,1), a sama klasa `.link-row`
   samo (0,1,0) — pa se na prelazak mišem podcrtavao CIJELI redak, zajedno s
   brojkom na desnom kraju i prazninom do nje. Inline stil koji je ovdje stajao
   prije toga nije trpio jer inline pobjeđuje sve; klasa ne. */
.link-row, .link-row:hover { color: inherit; text-decoration: none; }
.link-row:hover .link-row-name { text-decoration: underline; }

/* ═══ Heatmapa ════════════════════════════════════════════════════════════
   Mreža redak × tjedan, u kojoj boja nosi KOLIČINU (v. `--heat-*` u
   tokens.css). Jedan grid za sve retke, a ne grid po retku: stupci se moraju
   poklapati kroz cijelu tablicu, a to je ono što `grid` radi sam.

   🔑 BEZ CANVASA. Ovo nije graf nego tablica koja ima boju — a tablica u DOM-u
   ima naslov ćelije na `title`, označiv tekst i ispis, i ne traži biblioteku
   koja se učitava samo zbog nje (§7.4: „iscrpi CSS i nativno, pa tek onda JS").

   Širina ćelije se ne zadaje nego dijeli (`1fr`), pa ista mreža služi i za 13
   tjedana i za 52; donja granica je na spremniku (`min-width`), koji ispod nje
   daje vodoravni scroll umjesto ćelija od tri piksela.                       */
/* Ćelija se DIJELI do gornje granice, ne rasteže bez nje: s pedeset tjedana
   dijeljenje daje ~14 px, ali s pet bi dalo 200 px — mreža bi se pretvorila u
   trake i prestala se čitati kao mreža. Donja granica je na spremniku
   (`min-width`), koji ispod nje daje vodoravni scroll. */
.heat {
  display: grid;
  grid-template-columns: var(--heat-label, 190px) repeat(var(--heat-cols), minmax(0, 22px)) auto;
  gap: 2px;
  align-items: center;
  /* Višak mjesta ostaje NA KRAJU, inače ga pokupi stupac sa zbrojem i odseli
     se na desni rub, daleko od retka kojem pripada. */
  justify-content: start;
  min-width: calc(var(--heat-label, 190px) + var(--heat-cols) * 13px + 60px);
}

.heat-name {
  font: var(--type-body-sm);
  color: var(--md-on-surface);
  padding-right: .5rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Mjesec je KRUPNIJA oznaka nad tjednima, s crtom na svom početku — tjedan se
   ne da označiti pojedinačno (52 natpisa), a bez ikakve oznake se ne zna gdje
   se u godini gleda. */
.heat-month {
  font: var(--type-label-sm);
  color: var(--md-on-surface-variant);
  padding-left: 3px;
  border-left: 1px solid var(--md-outline-variant);
  overflow: hidden;
  white-space: nowrap;
}

.heat-cell {
  height: 16px;
  border-radius: 2px;
  background: var(--heat-0);
}
.heat-cell.h1 { background: var(--heat-1); }
.heat-cell.h2 { background: var(--heat-2); }
.heat-cell.h3 { background: var(--heat-3); }
.heat-cell.h4 { background: var(--heat-4); }
.heat-cell.h5 { background: var(--heat-5); }

/* Obrub umjesto promjene boje: boja ćelije JE podatak, pa je hover ne smije
   dirati — inače se ćelija pod mišem čita kao drugačija vrijednost. */
.heat-cell:hover { outline: 2px solid var(--md-on-surface); outline-offset: -1px; }

/* „Ostali" i „Interno" nisu klijenti, pa nose isti raspored svjetline bez
   identiteta boje. `grayscale` je ovdje ispravan alat baš zato što čuva
   svjetlinu koraka — a svjetlina je jedino što u rampi nešto znači. */
.heat-cell.soft { filter: grayscale(1); }
.heat-name.soft { color: var(--md-on-surface-variant); }

/* ── Zadnji redak: ukupno zauzeće ──────────────────────────────────────────
   Drugi hue i razmak iznad, jer ne odgovara na isto pitanje kao redci nad
   njim: oni kažu TKO, on kaže KOLIKO NAS JE PUNO. Razmak radi `margin-top` na
   samim ćelijama — u jednom gridu nema retka na koji bi se objesio. */
.heat-sum { margin-top: 7px; }
.heat-cell.l1 { background: var(--load-1); }
.heat-cell.l2 { background: var(--load-2); }
.heat-cell.l3 { background: var(--load-3); }
.heat-cell.l4 { background: var(--load-4); }
.heat-cell.l5 { background: var(--load-5); }
.heat-name.heat-sum { font: var(--type-label-md); }

.heat-total {
  font: var(--type-body-sm);
  color: var(--md-on-surface-variant);
  padding-left: .5rem;
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Legenda je ista ćelija u malom — ne novi oblik, da se veza s mrežom ne mora
   objašnjavati. */
.heat-key { display: inline-flex; align-items: center; gap: 2px; }
.heat-key .heat-cell { width: 16px; }

/* ═══ Oblačić (tooltip) ═══════════════════════════════════════════════════
   🔑 ISTI IZGLED KAO OBLAČIĆ GRAFA, koji dolazi iz Chart.js-a: obrnuta ploha,
   10 px razmaka, radijus 8 px (v. `chartTooltip()` u charts.js). Dva prikaza
   na istoj kartici ne smiju imati dva različita oblačića.

   `position: fixed` jer se računa iz `getBoundingClientRect()`, a `pointer-
   events: none` da oblačić ne otme miša ćeliji nad kojom stoji.             */
.tip {
  position: fixed;
  z-index: 90;
  pointer-events: none;

  /* 🔑 POMIČE SE `transform`-om, a kut ostaje na 0,0 — i to nije stilski izbor
     nego uvjet da se može izmjeriti. Dok je stajao na `left/top`, uz desni rub
     ekrana se prelamao da stane u preostalu širinu, pa se mjerio uži nego što
     jest i odluka o preskoku na drugu stranu ispadala kriva. Na 0,0 mu je
     širina uvijek prirodna, a `transform` je usput i jeftiniji za animaciju. */
  left: 0;
  top: 0;
  transform: translate3d(0, 0, 0);

  opacity: 0;
  /* IZLAZ je mekši od ulaza: oblačić koji nestane naglo čita se kao greška, a
     onaj koji se pojavi sporo kao zastoj. Zato duže trajanje stoji na osnovnom
     stanju (u koje se pada kad `on` nestane), a kraće na `on`. */
  transition: opacity var(--dur-medium) var(--ease-standard);
}

/* Dok je vidljiv, PUTUJE do sljedeće točke umjesto da skoči — isto ponašanje
   koje ima ugrađeni oblačić Chart.js-a na prstenovima. Prijelaz `transform`-a
   stoji SAMO ovdje: da stoji i na skrivenom stanju, oblačić bi pri svakom
   novom pojavljivanju doletio s prošlog mjesta.

   Trajanje je namjerno DUŽE od uobičajenog prijelaza u sučelju: oblačić prati
   kursor koji se kreće brzo i često, pa kratko trajanje ne čita se kao brzina
   nego kao nemir. */
.tip.on {
  opacity: 1;
  transition: opacity var(--dur-short) var(--ease-standard),
              transform var(--dur-long) var(--ease-emphasized);
}

/* ── Tri sloja, i svaki postoji zbog jedne stvari ─────────────────────────
   `.tip`       — položaj i vidljivost (ovdje je `transform`, ovdje je `opacity`)
   `.tip-box`   — mjera i njezina animacija; ima `overflow: hidden`
   `.tip-inner` — sadržaj u svojoj prirodnoj veličini

   🔑 ZAŠTO TRI, A NE DVA. `width`/`height` se ne daju animirati iz `auto`, pa
   okvir mora nositi IZMJERENU mjeru u pikselima, a mjeru netko mora dati —
   `.tip-inner` je `max-content` i uvijek zna svoju pravu. Rezanje koje pritom
   treba (`overflow: hidden`) odrezalo bi strelicu, pa strelica ne smije biti u
   tom okviru nego iznad njega, u `.tip`.

   Širina je veća nego što treba heatmapi, jer isti oblačić nosi i razradu
   grafa: „Klijent - Projekt" je dugačak niz, a u canvasu se nije mogao ni
   prelomiti. */
.tip-box {
  overflow: hidden;
  border-radius: var(--shape-sm);
  background: var(--md-inverse-surface);
  color: var(--md-inverse-on-surface);
  font: var(--type-body-sm);
  box-shadow: var(--elev-2);
}
.tip.on .tip-box {
  transition: width var(--dur-long) var(--ease-emphasized),
              height var(--dur-long) var(--ease-emphasized);
}

.tip-inner {
  width: max-content;
  max-width: 360px;
  padding: 10px;
}

/* Strelica je KVADRAT ZAKRENUT ZA 45°, a ne trokut od obruba: obrubni trokut
   ne može naslijediti boju plohe (`background` ga ne dira), pa bi tema morala
   biti upisana dvaput. Stoji IZA okvira (`z-index: -1`), pa se vidi samo
   izbočena polovica — druga je pod neprozirnom plohom oblačića. */
.tip-arrow {
  position: absolute;
  z-index: -1;
  width: 10px;
  height: 10px;
  background: var(--md-inverse-surface);
  transform: rotate(45deg);
}
.tip.on .tip-arrow {
  transition: left var(--dur-long) var(--ease-emphasized),
              top  var(--dur-long) var(--ease-emphasized);
}
.tip b { display: block; font: var(--type-label-md); margin-bottom: .125rem; }

/* Kvadratić boje uz seriju — isti znak koji je crtao canvas, da se oblačić
   grafa ne promijeni time što je izašao iz njega. */
.tip-key {
  display: inline-block;
  width: 10px; height: 10px;
  margin-right: 6px;
  border-radius: 2px;
  /* `middle` poravnava na sredinu x-visine, a ne na optičku sredinu retka, pa
     kvadratić sjedne prenisko uz brojke i velika slova. Dva piksela gore i
     poravnat je s tekstom koji ga slijedi. */
  vertical-align: middle;
  position: relative;
  top: -2px;
}

/* Razrada ispod zbroja: prvi redak nosi razmak, dalje se nižu. U canvasu je taj
   razmak bio prazan redak teksta — ovdje je margina, pa se ne mora crtati. */
.tip-note { color: var(--md-inverse-on-surface); opacity: .85; }
/* Razmak nosi PRVI redak razrade, prepoznat po tome što mu prethodi redak koji
   razrada nije. `:first-of-type` ovdje ne radi — ono gleda prvi `div` među
   braćom, a to je redak serije. */
:not(.tip-note) + .tip-note { margin-top: .5rem; }

/* ═══ Utility klase ═══════════════════════════════════════════════════════
   🔑 STOJE NA KRAJU, i to namjerno.

   Utility je svjesno odstupanje od zadanog izgleda bloka („ovaj stupac je
   ipak desno"), pa MORA moći nadjačati blok. `.table th, .table td` je
   `(0,1,1)` — jednako specifično kao `td.num`, a učitava se kasnije, pa bi
   utility iz `base.css` tiho gubio. Ovdje pobjeđuje redoslijedom, bez
   `!important` i bez napuhavanja selektora.                                 */

/* `.num` je DESNO I TABULARNO (tabularne znamenke dolaze iz `base.css`), i to
   dvoje ide zajedno iz istog razloga: sadržaj koji varira širinom znamenki
   čita se lakše kad mu je desni rub poravnat, a to vrijedi tek ako su same
   znamenke jednako široke. Datum je takav sadržaj kao i iznos, pa nosi istu
   klasu — zasebna klasa „samo desno" bi zavarala da je riječ o drugoj vrsti
   stupca.

   🔑 VRIJEDI I ZA SLOBODAN UNOS KOJI JE PRETEŽITO BROJČAN — broj računa (IRA),
   rok koji je u pravilu datum. Takav stupac tehnički jest tekst i rijetka
   iznimka u njemu postoji („ŠTA PRIJE", „R-12/26"), ali **iznimka ne određuje
   kako stupac izgleda**: čitatelj ondje traži broj i uspoređuje ga s brojevima
   iznad i ispod. Zato nosi `.num`, a ne `.body-sm` iz tekstualne obitelji —
   inače u nizu susjednih datuma jedan stupac ispadne sitniji i lijevo
   poravnat, pa se tri stupca iste vrste čitaju kao tri različite. */
.center, td.center, th.center { text-align: center; }
.num,    td.num,    th.num    { text-align: right; }
.left,   td.left,   th.left   { text-align: left; }
.nowrap { white-space: nowrap; }
/* Ćelija uz GORNJI rub retka. Ćelije su zadano centrirane okomito, pa se u
   retku gdje jedan stupac nosi dva-tri reda teksta kratki susjedi spuste na
   njegovu sredinu — iznos tada više ne stoji u liniji s nazivom uz koji ide.
   Poravnanje je utility, kao i vodoravna: traži se na ĆELIJI, a ne na tablici. */
.align-top, td.align-top, th.align-top { vertical-align: top; }

/* Bez donjeg razmaka. Postoji zbog `.title`, koji ga nosi jer naslov retka
   gotovo uvijek stoji nad podnaslovom — ali ne kad je jedini u retku ili kad
   je stavka flex-retka: ondje `align-items: center` razmak pretvori u pomak
   prema gore, pa se istaknuti redak ne poravna s ostalima. */
.flush { margin-bottom: 0; }

/* ===========================================================================
   Isplativost — lepeza i ljestvica klijenata.  (SCOPE §7.4-F, §7.6.11)
   =========================================================================== */

/* ── Razilazna rampa: gubitak → profit ──────────────────────────────────────
   🔑 ZAŠTO NE `--chart-*` NI `--heat-*`. `--chart-*` je identitet (koji
   projekt), `--heat-*` je količina u jednom smjeru. Ovdje mjera ima SREDINU
   koja nešto znači — nulu — i dva suprotna smjera od nje. To traži razilaznu
   rampu: blijedo uz nulu, zasićeno na krajevima.

   🔑 NULA SE PRIBIJA, NE RAČUNA IZ RASPONA. Sredina je marža 0 %, a ne sredina
   opaženih vrijednosti — inače u dobrom razdoblju najlošiji PROFITABILAN
   projekt ispadne crven. Isto pravilo koje §7.4-E primjenjuje na traku
   zauzeća: koraci su vezani uz prag, ne uz najjaču ćeliju.

   ⚠️ Tamna tema nije preslikana svijetla. Na svijetloj plohi krajnost se
   pokazuje TAMNIJOM bojom, na tamnoj SVJETLIJOM — preslikavanje bi značilo da
   najgori projekt na tamnoj temi nestane u pozadini. */
:root {
  --profit-l3: #B23A28;   /* gubitak preko 20 %  */
  --profit-l2: #D4705E;   /* gubitak 10–20 %     */
  --profit-l1: #E7A79C;   /* gubitak do 10 %     */
  --profit-p1: #C3B8EE;   /* profit do 15 %      */
  --profit-p2: #8E7BD8;   /* profit 15–30 %      */
  --profit-p3: #5A48BE;   /* profit preko 30 %   */
}

[data-theme="dark"] {
  --profit-l3: #F26B52;
  --profit-l2: #D9705F;
  --profit-l1: #B8756B;
  --profit-p1: #8B80C2;
  --profit-p2: #A594EC;
  --profit-p3: #C4B5FF;
}

/* ── Lepeza ─────────────────────────────────────────────────────────────────
   Bez biblioteke i bez canvasa: dvanaest krugova u četvrtini diska je SVG koji
   server ionako zna izračunati. Chart.js ovdje ne bi crtao ništa što HTML ne
   može, a `charts.js` bi postao datoteka o dva različita grafa — pa bi greška
   u jednom rušila drugi. (§7.6.7: iscrpi nativno prije JS-a.) */
.fan { display: block; width: 100%; height: auto; max-width: 560px; margin: 0 auto; }

.fan-grid   { fill: none; stroke: var(--md-outline-variant); stroke-width: 1; }
.fan-ray    { stroke: var(--md-outline-variant); stroke-width: 1; }

/* Nulta crta nosi cijelo značenje grafa — iznad se zarađuje, ispod gubi — pa
   je jedina puna i jedina u boji teksta. Ostale zrake su orijentiri. */
.fan-zero   { stroke: var(--md-on-surface); stroke-width: 1.5; }

.fan-label  { font: var(--type-label-sm); fill: var(--md-on-surface-variant); }
.fan-zero-label { font: var(--type-label-sm); fill: var(--md-on-surface); font-weight: 700; }

.fan-bub {
  stroke: var(--md-surface);
  stroke-width: 1.5;
  cursor: pointer;
  transition: opacity .12s ease;
}
.fan-bub.l3 { fill: var(--profit-l3); }
.fan-bub.l2 { fill: var(--profit-l2); }
.fan-bub.l1 { fill: var(--profit-l1); }
.fan-bub.p1 { fill: var(--profit-p1); }
.fan-bub.p2 { fill: var(--profit-p2); }
.fan-bub.p3 { fill: var(--profit-p3); }

/* Isticanje se radi PRIGUŠENJEM OSTALIH, ne povećanjem odabranog: krug koji
   naraste pomakne se preko susjeda, a veličina ovdje nosi podatak (cijenu) —
   pa bi rastući krug kratko lagao. */
.fan:hover  .fan-bub             { opacity: .55; }
.fan .fan-bub:hover,
.fan .fan-bub:focus-visible      { opacity: 1; }
.fan-bub:focus-visible           { outline: 2px solid var(--md-primary); outline-offset: 2px; }

/* Prikliješten projekt — marža izvan ±50 %. Bez oznake bi sjeo na rub i čitao
   se kao granični slučaj, a nije: on je izvan skale. */
.fan-clamp  { fill: none; stroke: var(--md-on-surface); stroke-width: 1.5; }

.fan-legend { display: flex; flex-wrap: wrap; gap: .35rem .9rem; align-items: center; }
.fan-key    { display: inline-flex; align-items: center; gap: .35rem; }
.fan-key i  { width: 12px; height: 12px; border-radius: 50%; display: block; }

/* ── Ljestvica ──────────────────────────────────────────────────────────── */

/* Ocjena je jedini broj u tablici koji je IZVEDEN, pa se i čita drukčije od
   izmjerenih stupaca pokraj sebe. */
.rank-score { font: var(--type-title-sm); font-variant-numeric: tabular-nums; }

/* Na koliko mjera ocjena počiva. Nije ukras: ocjena iz dvije mjere i ocjena iz
   pet nisu ista tvrdnja, a bez ovoga izgledaju identično. (§7.6.11) */
.rank-basis { font: var(--type-label-sm); color: var(--md-on-surface-variant); }

.rank-bar   { height: 6px; border-radius: 3px; background: var(--md-surface-variant); overflow: hidden; }
.rank-bar > i { display: block; height: 100%; border-radius: 3px; background: var(--md-primary); }

/* Oznaka luka stoji NA luku (v. `reports.php`), pa joj treba obrub u boji plohe
   — inače je linija luka presiječe po sredini i brojka postane nečitljiva. */
.fan-ring-label {
  font: var(--type-label-sm);
  fill: var(--md-on-surface-variant);
  paint-order: stroke;
  stroke: var(--md-surface);
  stroke-width: 4px;
  stroke-linejoin: round;
}

/* Rampa u legendi — isti koraci kojima je graf i nacrtan. */
.fan-ramp { display: flex; width: 240px; border-radius: 4px; overflow: hidden; }
.fan-ramp > i { flex: 1 1 0; height: 10px; }
.fan-ramp-scale { display: flex; justify-content: space-between; width: 240px; margin-top: .25rem; }

/* Natpis u oblačiću lepeze. Ondje nema kvadratića u boji (svaki mjehurić je
   jedan projekt, ne serija), pa redak vodi riječ — a prigušena je da vrijednost
   pokraj nje ostane ono što oko prvo uhvati. */
.tip-key-text { color: var(--md-inverse-on-surface); opacity: .85; }

/* ── Čip u PODIZBORNIKU ──────────────────────────────────────────────────────
   Traka kartica izvještaja nije skup filtara nego podizbornik: svi izbori
   moraju stati u JEDAN redak. Kad se prelomi, zadnja kartica izgleda
   drugorazredno iako je ravnopravna — a koja će to biti ovisi o širini prozora,
   pa ista kartica na dva ekrana ima dva značaja.

   🔑 Modifikator na TRAKU, ne na pojedini čip i ne preko položaja u stablu:
   gušća mjera je svojstvo podizbornika, pa se i imenuje tako. Čip drugdje
   (razdoblje, prekidači unutar kartica) ostaje nepromijenjen — ondje prelamanje
   ništa ne znači.

   Mjerena granica: s 9 kartica pri zadanoj mjeri (.85rem, label-lg) traka se
   lomi na širinama ispod ~1400 px; s .65rem i 12px stane do 10 kartica. Kad ih
   bude više od toga, rješenje nije još manji tekst nego prelijevanje sa
   scrollom. */
.chip-nav .chip { padding: 0 .65rem; font-size: .78rem; }

/* Vodilica od ishodišta do mjehurića pod pokazivačem. Isprekidana i u boji
   naglaska, da se ne pobrka sa zrakama marže — one su skala, ova je odgovor na
   „koliko je ovaj daleko". `[hidden]` na SVG elementu ne skriva sam po sebi. */
.fan-guide { stroke: var(--md-primary); stroke-width: 1.5; stroke-dasharray: 4 3; }
.fan-guide[hidden] { display: none; }

/* ── Lepeza + popis projekata uz nju ─────────────────────────────────────────
   Graf je bio centriran uz pola prazne kartice. Popis desno taj prostor koristi
   za ono što graf ne može dati: točan naziv i točan postotak.

   Lomi se na uski zaslon: ispod ~900 px popis ide POD graf, jer bi u dva stupca
   oboje bilo preusko da se pročita. */
.fan-wrap { display: flex; gap: 1.5rem; align-items: flex-start; flex-wrap: wrap; }
.fan-wrap > .fan { flex: 1 1 380px; margin: 0; }

.fan-list {
  flex: 1 1 320px;
  /* Vlastiti scroll umjesto kape: stotinu projekata ne smije razvući karticu,
     ali se ni jedan ne smije prešutjeti. Visina prati graf. */
  max-height: 480px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* Gumb, ne `div`: redak je upravljački element (istakne mjehurić), pa mora
   raditi i tipkovnicom — i mora to najaviti, a ne samo reagirati na miša. */
.fan-item {
  display: flex; align-items: center; gap: .6rem;
  width: 100%; padding: .3rem .5rem;
  border: 0; border-radius: var(--shape-xs);
  background: transparent;
  font: var(--type-body-sm);
  color: var(--md-on-surface);
  text-align: left;
  cursor: pointer;
}
.fan-item:hover, .fan-item:focus-visible { background: var(--md-surface-container-low); }
.fan-item.is-on { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }

/* Kružić nosi POJAS, ne identitet — v. bilješku u `reports.php`. Zato je i
   manji od mjehurića na grafu: da ne obeća točnu vezu koju ne može održati. */
.fan-dot { flex: 0 0 10px; height: 10px; border-radius: 50%; display: block; }
.fan-dot.l3 { background: var(--profit-l3); }
.fan-dot.l2 { background: var(--profit-l2); }
.fan-dot.l1 { background: var(--profit-l1); }
.fan-dot.p1 { background: var(--profit-p1); }
.fan-dot.p2 { background: var(--profit-p2); }
.fan-dot.p3 { background: var(--profit-p3); }

.fan-item-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fan-item-pct  { flex: 0 0 auto; font-weight: 600; }
.fan-item-pct.neg { color: var(--md-error); }

/* Isticanje vođeno IZ POPISA. `:hover` na grafu ne može pokriti taj slučaj —
   miš je tada nad popisom, pa graf nije ni u `:hover` stanju. */
.fan.is-focused .fan-bub      { opacity: .55; }
.fan .fan-bub.is-on           { opacity: 1; }

/* ── Tablica sa ZAMRZNUTIM OKVIROM ───────────────────────────────────────────
   Za matricu: stupci su imena zaposlenika, redci su projekti. Otkotrlja li se
   bilo koje od toga s ekrana, ostane mreža brojeva bez ijedne oznake — brojka
   bez „tko" i bez „na čemu" ne znači ništa.

   Zamrzava se sve četvero: zaglavlje (tko), zbrojni redak (ukupno po osobi),
   prvi stupac (koji projekt) i zadnji (ukupno po projektu). Klize samo
   zaposlenici u sredini, što je jedina dimenzija koja doista raste.

   🔑 ZAŠTO OKVIR MORA DOBITI VISINU. `position: sticky` lijepi se za najbliži
   SKROLAJUĆI predak, a to je već `.table-wrap` — `overflow-x: auto` po
   specifikaciji čini i okomitu os `auto`, pa okvir jest skrolna kutija. Dok mu
   je visina slobodna, okomito se nikad ne skrola (skrola stranica), pa se
   zaglavlje nema za što zalijepiti. Bez `max-height` okomito ljepljenje ne bi
   radilo ništa — to je jedini razlog zašto ovdje postoji ugniježđeni scroll.

   🔑 ZAŠTO `border-collapse: separate`. Uz `collapse` rubovi pripadaju
   ZAJEDNIČKOJ mreži tablice, a ne pojedinoj ćeliji — pa ljepljiva ćelija otputuje
   bez svoje crte i redci se vide kako prolaze kroz zaglavlje. Uz `separate` i
   `border-spacing: 0` izgled je isti (postoje samo donji rubovi, pa se ništa ne
   udvostručuje), ali crta putuje sa ćelijom.

   ⚠️ Modifikator, ne promjena `.table-wrap`: ostale tablice nemaju ovaj problem
   i ne smiju dobiti ugniježđeni scroll koji im ne treba.

   Slojevi: tijelo 1, zaglavlje i podnožje 2, četiri ugla 3 — inače bi ugao
   prošao ispod susjednog ljepljivog retka ili stupca. */
.table-wrap.keep-frame { max-height: min(72vh, 760px); }

.table-wrap.keep-frame .table { border-collapse: separate; border-spacing: 0; }

/* Zamrznuti stupci — prvi (koji projekt) i zadnji (ukupno po projektu). */
.table-wrap.keep-frame tbody td:first-child,
.table-wrap.keep-frame tbody td:last-child {
  position: sticky;
  z-index: 1;
  background: var(--md-surface-container-lowest);
}
.table-wrap.keep-frame tbody td:first-child { left: 0;  border-right: 1px solid var(--md-outline-variant); }
.table-wrap.keep-frame tbody td:last-child  { right: 0; border-left:  1px solid var(--md-outline-variant); }

/* Zamrznuti redci — zaglavlje i zbroj. */
.table-wrap.keep-frame thead th,
.table-wrap.keep-frame tfoot td {
  position: sticky;
  z-index: 2;
  background: var(--md-surface-container-lowest);
}
.table-wrap.keep-frame thead th { top: 0; }
/* Podnožje nosi VLASTITU gornju crtu: donji rub zadnjeg retka tijela otklizi
   pod njega, pa bi zbroj inače visio bez razdjelnice. */
.table-wrap.keep-frame tfoot td { bottom: 0; border-top: 1px solid var(--md-outline-variant); }

/* Četiri ugla su ljepljiva u OBA smjera, pa moraju i stajati iznad oboje. */
.table-wrap.keep-frame thead th:first-child,
.table-wrap.keep-frame tfoot td:first-child { left: 0;  z-index: 3; border-right: 1px solid var(--md-outline-variant); }
.table-wrap.keep-frame thead th:last-child,
.table-wrap.keep-frame tfoot td:last-child  { right: 0; z-index: 3; border-left:  1px solid var(--md-outline-variant); }

/* ── Evidencija angažmana: ČETIRI zamrznuta lijeva stupca ────────────────────
   (SCOPE §7.6.14)

   🔑 ZAŠTO BAŠ ČETIRI. Toliko ih je zamrznuto i u njezinu Excelu — `RB` ·
   `status` · `klijent` · `vrsta angažmana`. To je zatečena navika, ne naša
   procjena koliko je dosta: bez njih se otkotrlja „tko" i „što", pa ostane
   mreža datuma i iznosa bez ijedne oznake.

   🔑 ZAŠTO FIKSNE ŠIRINE, A NE MJERENJE U JS-u. Jedan ljepljivi stupac stoji na
   `left: 0` i ne treba znati ničiju širinu; svaki sljedeći mora početi na
   ZBROJU širina onih prije njega. Taj se zbroj dobiva ili mjerenjem u skripti
   (pa raspored ovisi o tome je li se skripta izvela i je li font već učitan),
   ili nametnutim širinama — a onda je zbroj konstanta koju i CSS zna. Druga je
   skuplja za jedan ustupak u izgledu, a jeftinija za sve ostalo.

   Pomaci: 0 · 104 · 264 · 454 — zbrojevi širina 104/160/190/160.

   🔑 Širina ZADNJEG zamrznutog stupca ne ulazi ni u čiji pomak, pa se smije
   mijenjati sama. Svaka prije njega ne smije.

   ⚠️ ŠIRINA PRVOG STUPCA NIJE UKRAS NEGO UVJET DA OVO RADI. Ćelija naraste
   preko zadane širine čim joj sadržaj to traži, a pomaci ostalih stupaca su
   KONSTANTE — pa prvi preširok redni broj gurne stupac ispod susjednog i
   zamrznuti se okvir preklopi. Zatečenih 56 px držalo je samo jednoznamenkasti
   broj: „1" je stalo, a „12/2024" (njezin stvarni oblik) i značka „za 2025."
   nisu. Izmjereno u pregledniku: stupac je narastao na 101 px, a `frozen-2` je
   i dalje počinjao na 56 — otuda preklop. 104 px drži oboje, s malo zraka.

   🔑 Mijenja li se ova širina, mijenjaju se i SVA tri pomaka ispod. Jedan broj
   bez ostalih je upravo kvar koji se ovdje popravlja.

   ⚠️ ZAMRZNUTOST VISI NA KLASI ĆELIJE (`.frozen-1`…`.frozen-4`), ne na
   `nth-child`. Rate su PODREDCI: ugovorne ćelije se protežu kroz njih
   (`rowspan`), pa drugi redak rate uopće nema prva četiri stupca — po položaju
   bi mu se zamrznuo uvjet plaćanja. Klasa kaže ŠTO ćelija jest, a položaj samo
   gdje se zatekla.

   ⚠️ Ovo je modifikator UZ `.keep-frame`, ne umjesto njega: ljepljivo zaglavlje,
   `border-collapse: separate` i visina okvira dolaze odande i ovdje vrijede
   nepromijenjeni. */
.table-wrap.keep-left-4 .table .frozen-1,
.table-wrap.keep-left-4 .table .frozen-2,
.table-wrap.keep-left-4 .table .frozen-3,
.table-wrap.keep-left-4 .table .frozen-4 {
  position: sticky;
  background: var(--md-surface);
  z-index: 1;
}
.table-wrap.keep-left-4 .table thead .frozen-1,
.table-wrap.keep-left-4 .table thead .frozen-2,
.table-wrap.keep-left-4 .table thead .frozen-3,
.table-wrap.keep-left-4 .table thead .frozen-4 { z-index: 3; }

/* ⚠️ POLOŽAJNO ZAMRZAVANJE IZ `.keep-frame` SE OVDJE GASI. Ondje su zamrznuti
   `td:first-child` i `td:last-child` — matrici u izvještajima to je točno, jer
   ondje svaki redak doista ima prvi i zadnji stupac. Ovdje nema: rata je
   PODREDAK, pa joj je prva ćelija UVJET PLAĆANJA, a zadnja datum naplate. Dvije
   definicije zamrznutosti tada rade jedna protiv druge.

   *Izmjereno u pregledniku, ne pretpostavljeno:* uz vodoravni pomak od 700 px
   ćelija uvjeta bila je `sticky`, `z-index: 1`, na 566–662 px — točno preko
   zamrznutog stupca „Vrsta angažmana" koji drži 479–669 px. Kako dolazi kasnije
   u dokumentu uz isti sloj, iscrtavala se PREKO njega: tekst uvjeta virio je
   ispod zamrznutog okvira. Isto se događalo i uz desni rub.

   🔑 `.keep-frame` se NE dira — ondje je ispravno. Gasi se samo ovdje, i samo
   na ćelijama koje nisu zamrznute po klasi: `:not()` čuva `frozen-1`, koji JEST
   prva ćelija svog retka i mora ostati ljepljiv. */
.table-wrap.keep-left-4 .table tbody td:first-child:not([class*="frozen-"]),
.table-wrap.keep-left-4 .table tbody td:last-child:not([class*="frozen-"]) {
  position: static;
  background: none;
  border-left: 0;
  border-right: 0;
}

/* ⚠️ I ZAGLAVLJE, iz istog razloga — ali drukčijim potezom. Tijelu se ljepljivost
   gasi u cijelosti; zaglavlju se gasi SAMO vodoravno prikivanje, jer okomito
   (`top: 0`) ovdje i dalje treba. `position: static` bi odnio oboje.

   Bez ovoga zadnji naslov ostaje prikovan uz desni rub dok podaci ispod njega
   klize — pa „Arh. pismo" stoji nad tuđim stupcem. Zamrznuta su ČETIRI LIJEVA
   stupca (§7.6.14), desni rub nije dio toga. */
.table-wrap.keep-left-4 .table thead th:last-child:not([class*="frozen-"]),
.table-wrap.keep-left-4 .table tfoot td:last-child:not([class*="frozen-"]) {
  right: auto;
  z-index: 2;
  border-left: 0;
}

.table-wrap.keep-left-4 .table .frozen-1 { left: 0;     width: 104px; min-width: 104px; }
.table-wrap.keep-left-4 .table .frozen-2 { left: 104px; width: 160px; min-width: 160px; }
.table-wrap.keep-left-4 .table .frozen-3 { left: 264px; width: 190px; min-width: 190px; }
/* Zadnji zamrznuti nosi crtu koja ODVAJA okvir od onoga što klizi ispod njega —
   bez nje se ne vidi gdje prestaje zamrznuto, pa se klizanje čita kao pomak
   cijele tablice. */
.table-wrap.keep-left-4 .table .frozen-4 {
  left: 454px; width: 160px; min-width: 160px;
  border-right: 1px solid var(--md-outline-variant);
}

/* ── Mapa angažmana (SCOPE §7.4-A) ───────────────────────────────────────────
   Zaposlenici lijevo, klijenti desno, veze kao vrpce. SVG stiže iscrtan sa
   servera — ovdje su samo boje i stanja, kao kod lepeze (§7.4-F). Bez skripte
   se graf i dalje vidi; gubi se isticanje i oblačić, ne sadržaj. */
.emap-wrap { overflow-x: auto; }

/* `max-width` drži graf čitljivim na širokom zaslonu: razvučen preko 1100 px
   niti postaju gotovo vodoravne i prestaju se razlikovati jedna od druge. */
.emap { display: block; width: 100%; height: auto; max-width: 1100px; margin: 0 auto; min-width: 680px; }

/* 🔑 BOJA ČVORA ŽIVI U JEDNOJ VARIJABLI, ne u nizu pravila za `fill`.
   Stanja (prigušeno / istaknuto / povezano) moraju vraćati „vlastitu boju tog
   čvora" — bez varijable bi se svako od njih pisalo triput: jednom po strani i
   jednom za interno. Varijabla se nasljeđuje kroz SVG kao i bilo gdje drugdje.

   🔑 Strana se čita iz ATRIBUTA koji ionako postoji (`data-u` / `data-t`), pa
   nema klase koja bi mogla ispasti iz koraka s njim. */
.emap-node[data-u] { --emap-node: var(--md-primary); }
.emap-node[data-t] { --emap-node: var(--cat-4); }

/* 🔑 Nit nosi GRADIJENT iz boje lijevog u boju desnog stupca, pa joj se krajevi
   stapaju s trakama iz kojih izlazi i u koje ulazi. Sam gradijent je u SVG-u
   (jedan za sve niti, v. `reports.php`), a boje su ovdje — tako tamna tema ne
   traži drugi `<defs>`.

   ⚠️ Time nit više NE nosi identitet osobe, nego SMJER. To je i bila namjera:
   paleta ima šest koraka, a zaposlenika može biti devet — bojanje po osobi bi
   sedmome dalo boju prvoga i dvije osobe bi postale jedna. */
.emap-stop-a { stop-color: var(--md-primary); }
.emap-stop-b { stop-color: var(--cat-4); }

.emap-thread {
  fill: url(#emap-grad);
  /* Puna boja na četrdesetak niti je kaša, a siva ne pokazuje smjer. Ovoliko
     propušta oblik snopa i zadržava vezu s bojama stupaca. */
  opacity: .36;
  transition: opacity .12s ease;
}

.emap-bar { fill: var(--emap-node); transition: fill .12s ease; }

.emap-hit   { fill: transparent; }
.emap-name  { font: var(--type-body-sm); fill: var(--md-on-surface); }
.emap-hours { font: var(--type-label-sm); fill: var(--md-on-surface-variant); }
/* 🔑 Zbirno i nenaplativo razlikuje KURZIV, ne druga boja trake. Siva traka za
   „Interno" je probana pa odbačena: u stupcu u kojem su sve ostale trake iste
   boje, jedna siva se čita kao „isključeno" ili „bez podataka", a interni rad
   je odrađen rad. Kurziv kaže „druga vrsta stavke" bez te tvrdnje — i dovoljan
   je, jer stavka ionako stoji na stalnom mjestu, na dnu. */
.emap-node.is-rest .emap-name,
.emap-node.is-internal .emap-name { fill: var(--md-on-surface-variant); font-style: italic; }

.emap-node { cursor: default; }
.emap-node:focus { outline: none; }
/* Obris u boji teksta, ne u `--md-primary`: lijevi stupci SU te boje, pa bi im
   fokusni obris bio nevidljiv. */
.emap-node:focus-visible .emap-bar { stroke: var(--md-on-surface); stroke-width: 2; }

/* ⚠️ Prigušivanje vozi KLASA NA PLATNU, ne `:hover` na niti.
   `.emap:hover .emap-thread { opacity: … }` bi radilo za lepezu, gdje se ističe
   ono što je POD pokazivačem. Ovdje se ističe nit koja iz čvora IZLAZI — a ona
   pod pokazivačem nije. Zato klasu postavlja `engagement.js`; bez njega sve
   ostaje u mirnom stanju i graf je i dalje točan. */
/* Istaknuta nit zadržava GRADIJENT, mijenja joj se samo prozirnost — fill koji
   bi je prebojao u jednu boju razvezao bi joj krajeve od traka. */
.emap.is-focused .emap-thread       { opacity: .07; }
.emap.is-focused .emap-thread.is-on { opacity: .95; }

/* Prigušeni čvor gubi boju; istaknuti i povezani je zadržavaju — punu, bez
   prozirnosti. Prva izvedba je povezanu traku blijedila na 55 %, pa se nije
   vidjelo dokle niti dosežu: boja koja znači „ovamo ide rad" ne smije biti
   slabija od one koja znači „ovamo ne ide". Odakle se gleda i dalje se zna —
   to nosi podebljano ime, a ne jačina boje. */
.emap.is-focused .emap-bar                      { fill: var(--md-outline-variant); }
.emap.is-focused .emap-node.is-on .emap-bar,
.emap.is-focused .emap-node.is-linked .emap-bar { fill: var(--emap-node); }

.emap.is-focused .emap-name                       { fill: var(--md-on-surface-variant); }
.emap.is-focused .emap-node.is-on .emap-name,
.emap.is-focused .emap-node.is-linked .emap-name  { fill: var(--md-on-surface); }
.emap.is-focused .emap-node.is-on .emap-name      { font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  .emap-thread, .emap-bar { transition: none; }
}

/* ── Oblačić mape ────────────────────────────────────────────────────────────
   Glava nosi ime u boji i zbroj podebljan; razrada ispod ima obično ime i
   podebljanu vrijednost. Sadržaj gradi `engagement.js` jer `data-tip` zna samo
   retke teksta, a ovdje naglasci žive UNUTAR retka.

   ⚠️ OBLAČIĆ SE S TEMOM IZVRĆE — ploha mu je `--md-inverse-surface`, dakle
   tamna u svijetloj temi i SVIJETLA u tamnoj. Jedna roza zato ne može poslužiti
   objema: #FFA7D9 daje 7,33:1 na tamnoj plohi, a samo 1,38:1 na svijetloj.
   Tamna tema ima vlastiti korak istog tona (H 324°), najsvjetliji koji prolazi
   4,5:1 — isti postupak kao kod niza boja za grafove, gdje preslikavanje
   svijetle teme također nije prošlo provjeru. */
/* 🔑 `--cat-4-soft` je OVDJE jedini token koji ne treba parnjaka: `-soft`
   varijante se već izvrću s temom, baš kao i ploha oblačića, pa ime prolazi u
   obje (10,39:1 i 9,75:1). Druge dvije boje su fiksne i zato ih tamna tema mora
   imati vlastite — bijela ondje pada na 1,29:1, a prigušena na 1,14:1. */
.tip {
  --etip-name:   var(--cat-4-soft);
  --etip-strong: #FFFFFF;   /* 13,07:1 na tamnoj plohi */
  --etip-row:    #CCD6E3;   /*  8,90:1 */
}
[data-theme="dark"] .tip {
  --etip-strong: #15131A;   /* 14,28:1 na svijetloj plohi */
  --etip-row:    #4C6176;   /*  4,96:1 — isti ton (210°), spuštena svjetlina */
}

/* ⚠️ `.tip b` (0,1,1) pobjeđuje `.etip-head` (0,1,0), pa je glava zadržavala
   svojih .125rem i razmak se nije mijenjao — izmjereno 2px umjesto traženih
   ~4,8px. Element u selektoru je ovdje jedini način da se to nadjača bez
   `!important`. */
.tip b.etip-head { margin-bottom: .3rem; }

.etip-name {
  color: var(--etip-name);
  font-size: 14px;
  letter-spacing: .7px;
}
.etip-meta {
  font-weight: 500;
  color: var(--etip-strong);
  letter-spacing: .4px;
}

.etip-row { color: var(--etip-row); }
.etip-val {
  font-weight: 500;
  color: var(--etip-strong);
  letter-spacing: .4px;
}
/* Članovi zbirnog čvora („Ostali") su popis unutar popisa, pa idu uvučeno i
   prigušeno — inače se čitaju kao još jedan zaposlenik. */
.etip-row.is-sub { padding-left: .6rem; opacity: .85; }

/* ═══ Raspored resursa (modul `planning`, §7.8) ════════════════════════════
   Dvije cjeline s istim rječnikom: RADNA POVRŠINA (list godine) i DOKUMENT
   (ispis za ministarstvo). Vizualni jezik je posuđen od heatmape „Tko puni
   godinu" — uski tjedni stupci uz široki stupac oznake — ali nijedno pravilo
   nije preuzeto: ondje boja nosi količinu, ovdje je nosi ŠIRINA ISPUNE, a
   dokument je prava tablica (v. niže zašto).
   ========================================================================= */

/* ── List godine ───────────────────────────────────────────────────────── */

/* ⚠️ KONTROLE NOSE `.input.sm`, kao i svugdje izvan `.field` (filter-trake,
   ćelije tablica). Gole su dobivale `:focus-visible` iz `base.css` — outline od
   3 px i udubljenu sjenu preglednika — umjesto kućnog fokusa (rub u boji teme
   + mekana sjena). Ovdje se zato namješta samo ŠIRINA, ne izgled. */
.plan-grid .plan-hours { width: 6rem; text-align: right; }

/* Gumb u redu s poljima stoji na DNU, uz njihov donji rub — poravnanje reda je
   po vrhu (zbog polja različite visine), pa bi inače visio uz naslove. */
.plan-year-form .actions { align-self: flex-end; }

.plan-alloc { display: flex; flex-direction: column; gap: .35rem; }

/* Osoba — uloga — udio, u jednom retku. Imena su duga, postotak je kratak:
   prvi stupac se rasteže, ostali stoje. */
.plan-alloc-row {
  display: grid;
  grid-template-columns: minmax(8rem, 1fr) minmax(7rem, auto) 4.5rem auto auto;
  gap: .35rem;
  align-items: center;
}
.plan-alloc-unit { color: var(--md-on-surface-variant); font: var(--type-body-sm); }

/* „Dodaj osobu" se odvaja od zadnjeg retka — bez razmaka se čita kao još
   jedan redak uloge, a ne kao radnja nad njima. */
.plan-alloc + [data-alloc-add] { margin-top: 10px; }

/* Datum izvještaja u listu: izvorni datum ispod polja, prigušen. */
.plan-source { margin-top: .25rem; }

/* Zbroj udjela: tri stanja, ali bez treće boje — „previše" i „fali" su ista
   vrsta greške (nije 100 %), a razliku nosi oblačić s riječima. */
.plan-sum { font-weight: 600; }
.plan-sum.ok  { color: var(--md-success); }
.plan-sum.off { color: var(--md-error); }

.plan-warn { color: var(--md-error); }

/* Kapacitet: znak upozorenja stoji IZVAN TOKA, u stalno rezerviranom razmaku.
   Dok je bio običan znak u ćeliji, gurao je broj ulijevo i taj jedan redak
   ispadao je iz okomice s ostalima. */
/* ⚠️ `td.plan-cap`, ne `.plan-cap`: razmak ćelije postavlja `.table td` (0,1,1),
   pa bi ga goli razred (0,1,0) izgubio — znak bi ostao bez rezerviranog mjesta
   i pao preko zadnje znamenke. Ista zamka kao `.tip b.etip-head`. */
.table td.plan-cap { position: relative; padding-right: 1.7rem; }
.plan-cap-flag {
  position: absolute;
  right: .45rem;
  top: 50%;
  transform: translateY(-50%);
  font-size: .85em;
  line-height: 1;
}

/* Koraci ispod gumba — objašnjenje stoji POD njima, jer u istom redu izgleda
   kao još jedan gumb. */
.plan-actions { padding: 1rem 1.25rem; }
.plan-steps {
  margin: .75rem 0 0;
  padding-left: 1.2rem;
  font: var(--type-body-sm);
  color: var(--md-on-surface-variant);
}
.plan-steps li { margin-bottom: .2rem; }

/* Traka ispunjenosti na ekranu po osobi — isti crtež kao u dokumentu. */
.plan-bar {
  display: block;
  width: 7rem;
  height: .55rem;
  border-radius: 2px;
  background: var(--md-surface-variant);
  overflow: hidden;
}
.plan-person-gap td { color: var(--md-on-surface-variant); }

/* ── Dokument ──────────────────────────────────────────────────────────── */

.plan-doc-body { background: var(--md-surface-variant); margin: 0; padding: 0 0 2rem; }

.plan-doc-bar {
  display: flex;
  gap: .75rem;
  align-items: center;
  flex-wrap: wrap;
  padding: .75rem 1.25rem;
  background: var(--md-surface);
  border-bottom: 1px solid var(--md-outline-variant);
  position: sticky;
  top: 0;
  z-index: 2;
}
.plan-doc-hint { font: var(--type-body-sm); color: var(--md-on-surface-variant); flex: 1 1 24rem; }

/* Na ekranu je list papira, u ispisu samo sadržaj. */
.plan-doc-page {
  background: var(--md-surface);
  margin: 1.25rem auto;
  padding: 1.25rem;
  max-width: 1600px;
  box-shadow: var(--elev-1);
}

.plan-doc-head { display: flex; gap: 1rem; align-items: flex-end; margin-bottom: 1rem; }
.plan-doc-head h1 { margin: 0; font: var(--type-title-lg); }
.plan-doc-sub { font: var(--type-body-sm); color: var(--md-on-surface-variant); }
.plan-doc-date { margin-left: auto; font: var(--type-body-sm); }

.plan-doc {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  font: var(--type-body-sm);
}
.plan-doc th, .plan-doc td {
  border: 1px solid var(--md-outline-variant);
  padding: 0;
}
.plan-doc th.plan-doc-job, .plan-doc th.plan-doc-who, .plan-doc-month {
  background: var(--md-surface-variant);
  font-weight: 600;
  padding: .3rem .4rem;
  text-align: left;
}
.plan-doc-month { text-align: center; white-space: nowrap; overflow: hidden; }

/* ⚠️ ŠIRINU NOSI ĆELIJA ZAGLAVLJA, NE TIJELA. Uz `table-layout: fixed` stupce
   mjeri PRVI redak tablice, a to je `<thead>` — dok je širina stajala samo na
   `td`, svih 69 stupaca dobilo je jednakih 15 px: ime klijenta se lomilo u
   deset redaka, a redak narastao na 126 px. Izmjereno u pregledniku, iz koda
   se nije vidjelo. */
.plan-doc th.plan-doc-job, .plan-doc td.plan-doc-job { width: 15rem; }
/* 11,5 rem, a ne 10: uz ime sada stoje i dani i sati, i na 10 rem se „Marko
   Marić (9 d · 72 h)" lomio u drugi redak — pa su redci s dugim imenom bili
   viši od ostalih. Tjedan time gubi 0,4 px, ime dobiva cijeli redak. */
.plan-doc th.plan-doc-who, .plan-doc td.plan-doc-who { width: 11.5rem; }
.plan-doc td.plan-doc-job { padding: .35rem .5rem; vertical-align: top; }
.plan-doc td.plan-doc-who { padding: .3rem .5rem; }
.plan-doc-client  { font-weight: 600; }
.plan-doc-project { color: var(--md-on-surface-variant); }
.plan-doc-meta    { font-size: .72rem; color: var(--md-on-surface-variant); margin-top: .15rem; }
.plan-doc-min     { opacity: .8; }
.plan-doc-name    { font-weight: 500; }
/* Dani i sati uz ime: ista veličina, lakši rez — podatak uz ime, ne drugo ime. */
.plan-doc-hours   { font-weight: 400; color: var(--md-on-surface-variant); }
/* Uloga ispod imena, kurzivom: ista osoba zna nositi dvije, pa je to rečenica
   uz ime, a ne vlastiti stupac koji bi se u 90 % redaka ponavljao. */
.plan-doc-role    { font-style: italic; font-size: .72rem; color: var(--md-on-surface-variant); }

/* Ćelija je JEDAN TJEDAN. Visina prati dva retka teksta u stupcu osobe. */
.plan-doc-cell { height: 2.2rem; vertical-align: middle; position: relative; }

/* 🔑 Deblji rub na prvom tjednu mjeseca — ide kroz cijelu visinu tablice, od
   naslova do zadnjeg retka, pa se mjeseci čitaju bez brojanja stupaca. */
.plan-doc th.month-start, .plan-doc td.month-start { border-left: 2px solid var(--md-outline); }

/* 🔑 Traka stoji ONDJE GDJE SU DANI U TJEDNU (`--off`), a ne uvijek od lijevog
   ruba: raspored se puni unazad, pa prvi tjedan posla završava uz desni rub.
   ⚠️ Donja granica 1,2 mm: jedan dan je pri tjednu od ~2,6 mm širok 0,5 mm i
   nestao bi — a nestao bi baš na rubnim tjednima, koji označavaju početak i
   kraj posla. Svjesno preuveličavanje, jer je alternativa nevidljiv podatak.
   ⚠️ `left` se kroti istim `max()`-om: bez toga bi uvećana traka na zadnjem
   danu tjedna prešla preko desnog ruba ćelije. */
.plan-doc-fill {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  left: min(var(--off, 0%), 100% - max(var(--fill, 0%), 1.2mm));
  width: max(var(--fill, 0%), 1.2mm);
  height: .85rem;
  background: var(--md-primary);
  border-radius: 1px;
  /* ⚠️ STOJI IZVAN `@media print`, iako se tiče samo ispisa: unutar upita
     vrijedilo bi tek u ispisnom mediju, pa se ne bi dalo provjeriti ni iz
     `getComputedStyle` ni iz sonde. Na ekranu ne mijenja ništa. */
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

/* Skupina retaka jednog posla — deblji rub ih odvaja od sljedećeg. */
.plan-doc-group { border-bottom: 2px solid var(--md-outline); }

/* ── Pregled po osobi: ista tablica, jedan zaposlenik ──────────────────────
   🔑 SLOBODAN TJEDAN JE OBOJAN KROZ CIJELI STUPAC. Ekran postoji zbog jednog
   pitanja — kamo još stane posao — a na njega odgovara oblik, ne broj: zelena
   okomita vrpca kroz sve poslove je rupa u koju se nekoga može staviti. */
.plan-doc-cell.is-free { background: var(--plan-free); }

.plan-doc-person td.plan-doc-job,
.plan-doc-person th.plan-doc-job { width: 18rem; }

.plan-legend { display: inline-flex; align-items: center; gap: .35rem; }
.plan-legend-swatch {
  display: inline-block;
  width: 1.1rem;
  height: .85rem;
  border: 1px solid var(--md-outline-variant);
  border-radius: 2px;
}

.plan-doc-foot {
  margin-top: .75rem;
  font: var(--type-body-sm);
  color: var(--md-on-surface-variant);
}

/* ── Ispis ─────────────────────────────────────────────────────────────────
   🔑 A4 VODORAVNO: timeline je širok, a uspravna stranica bi tjedan stisnula
   na 2 mm. Okomito listanje je u redu — stotinjak poslova staje u ~15 stranica.

   ⚠️ `print-color-adjust: exact` NIJE UKRAS: preglednik po defaultu ne ispisuje
   pozadine, a cijeli je dokument niz obojenih traka — bez ovoga PDF izađe
   prazan. Korisnik to i dalje može ugasiti kvačicom „Pozadinska grafika", pa
   uputa o tome stoji i na samom ekranu.
   ------------------------------------------------------------------------- */
@media print {
  @page { size: A4 landscape; margin: 8mm; }

  .plan-doc-bar { display: none; }

  .plan-doc-body { background: #fff; padding: 0; }
  .plan-doc-page { margin: 0; padding: 0; max-width: none; box-shadow: none; }

  .plan-doc, .plan-doc-fill, .plan-doc-month,
  .plan-doc th, .plan-doc td { print-color-adjust: exact; -webkit-print-color-adjust: exact; }

  /* 🔑 Zaglavlje se ponavlja na svakoj stranici SAMO iz `<thead>` prave
     tablice — zbog ovoga dokument nije CSS grid kao heatmapa. */
  .plan-doc thead { display: table-header-group; }

  /* Redci jednog posla ostaju zajedno; prelomljena skupina izgubila bi
     oznaku posla, koja stoji u `rowspan` ćeliji prvog retka. */
  .plan-doc-group { break-inside: avoid; }

  .plan-doc td.plan-doc-job { width: 44mm; }
  .plan-doc td.plan-doc-who { width: 30mm; }
  .plan-doc-cell { height: 9mm; }
}

/* ═══ Hero: slika, veo, izjava, advent ════════════════════════════════════
   Nadogradnja na postojeći hero. BEZ SLIKE SE NIŠTA NE MIJENJA — pastelni
   blobovi i tamna slova ostaju zadano stanje, a sve ispod se uključuje tek uz
   `.has-image` odnosno `.advent`.                                           */

/* 🔑 UZ SLIKU NEMA BLOBOVA. Pastelne mrlje (`::before`/`::after`) su ukras za
   PRAZAN hero. Uz sliku nisu samo suvišne nego i štetne: obje stoje na
   `z-index: -1`, isto kao veo, a `::after` je u redoslijedu iscrtavanja IZA
   svih čvorova — pa je tercijarna mrlja padala preko vela u donjem lijevom
   kutu i ondje bojala sliku u ljubičasto. */
.hero.has-image { background: var(--md-surface-container-high); }
.hero.has-image::before,
.hero.has-image::after { content: none; }

.hero .hero-bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;              /* omjer hera se mijenja s ekranom, slika se prilagođava */
  z-index: -2;                    /* ispod veo (-1) i ispod sadržaja */
}

/* 🔑 VEO JE DIJAGONALAN, a ne ravan odozdo.
   Pozdrav je kratak i stoji gore lijevo, a redak ispod njega je dulji — pa
   tama treba ići od DOLJE LIJEVO prema GORE DESNO. Tako slova dobiju podlogu
   točno ondje gdje jesu, a desna strana slike ostaje gotovo otkrivena.
   ⚠️ Dvije zaustavne točke u sredini (.55 i .72) namjerno su blizu: jedan
   blagi prijelaz preko cijele plohe posivio bi i desni kut. */
.hero .hero-veil {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(
      28deg,
      rgb(0 0 0 / .38) 0%,
      rgb(0 0 0 / .22) 28%,
      rgb(0 0 0 / .04) 55%,
      rgb(0 0 0 / .02) 72%,
      rgb(0 0 0 / 0)   92%);
}

/* Slova preko slike: bijela, tek toliko prigušena da ne sijeku sliku.
   🔑 SJENA JE SJAJ, NE OBRUB. Pomak od 1 px s malim radijusom čita se kao
   tehnički potez — slovo s crtom ispod sebe. Ovdje je bez pomaka i širokog
   radijusa: tamno se skuplja OKO slova i samo utiša podlogu, a da se sjena
   kao takva ne primijeti. Dva sloja jer jedan širok je premekan za svijetli
   kadar, a jedan uzak je opet obrub. */
.hero.has-image .greeting,
.hero.has-image .sub,
.hero.has-image .hero-quote { color: #fff; opacity: .9; }
.hero.has-image .greeting { text-shadow: 0 0 6px rgb(0 0 0 / .2), 0 2px 9px rgb(0 0 0 / .32); }
.hero.has-image .sub,
.hero.has-image .hero-quote { text-shadow: 0 0 6px rgb(0 0 0 / .2), 0 2px 7px rgb(0 0 0 / .3); }
.hero.has-image .sub strong  { color: #fff; }
/* Potpis firme je jedino što NIJE prigušeno — on je oznaka, a ne poruka. */
.hero.has-image .company-badge { color: #fff; opacity: 1; }

.hero .hero-quote {
  margin: .35rem 0 0;
  font-style: italic;
  font-weight: 300;
  color: var(--md-on-surface-variant);
  max-width: 54ch;
}
.hero .hero-quote-author { font-style: normal; opacity: .8; margin-left: .35rem; }

/* ── Advent ──────────────────────────────────────────────────────────────
   Svaka pahulja je svoj čvor s vlastitim mjerama iz `hero_snow_flakes()`.
   Prije su bila tri sloja `radial-gradient` koja klize: jeftino, ali sloj se
   ponavlja svakih 160 px, pa je ista trojka u koloni prelazila ekran — i
   djelovala kao greška u iscrtavanju, ne kao snijeg.

   🔑 PAD I ZANOŠENJE SU DVA ČVORA. Obje animacije bi inače pisale po istom
   `transform` i ona druga bi pojela prvu. Vanjski `span` pada (`top`), a
   unutarnji `<i>` se njiše (`translateX`) — zato svaka pahulja ima dva
   elementa, a ne jedan.                                                     */
.hero.advent .hero-snow {
  position: absolute; inset: 0; z-index: 0;
  overflow: hidden;                 /* pahulja na rubu ne smije izaći iz hera */
  pointer-events: none;
}
.hero.advent .hero-flake {
  position: absolute;
  left: var(--x);
  width: var(--s); height: var(--s);
  animation: hero-flake-fall var(--d) linear var(--delay) infinite;
}
.hero.advent .hero-flake.half { animation-name: hero-flake-half; }
.hero.advent .hero-flake > i {
  display: block;
  width: 100%; height: 100%;
  border-radius: 50%;
  background: #fff;
  opacity: var(--o);
  filter: blur(var(--b, .3px));     /* mekoća raste s promjerom — v. hero.php */
  animation: hero-flake-sway var(--sd) ease-in-out var(--delay) infinite alternate;
}

/* Put do dna. */
@keyframes hero-flake-fall {
  0%   { top: -12px; opacity: 0; }
  9%   { opacity: 1; }
  88%  { opacity: 1; }
  100% { top: 100%; opacity: 0; }
}
/* 🔑 DIO IH SE UGASI NA POLA PUTA — inače svih osamnaest prijeđe istu
   udaljenost i oko ih počne čitati kao zavjesu. Postotci u `@keyframes` se ne
   daju parametrirati po pahulji, pa je drugi put drugo pravilo, a ne druga
   vrijednost. */
@keyframes hero-flake-half {
  0%   { top: -12px; opacity: 0; }
  14%  { opacity: 1; }
  44%  { opacity: .75; }
  66%  { opacity: 0; }
  100% { top: 72%; opacity: 0; }
}
@keyframes hero-flake-sway {
  from { transform: translateX(calc(var(--sw) * -.5)); }
  to   { transform: translateX(var(--sw)); }
}

/* 🔑 Kapica NIJE u heru nego u omotaču — hero reže (`overflow: hidden`) jer
   slika inače iscuri izvan zaobljenih kutova, a to je rezalo i vrh kapice.
   Ovako strši preko kuta, kako i treba. */
.hero-wrap { position: relative; }
.hero-wrap > .hero-hat {
  position: absolute; top: -17px; right: -28px;
  /* Mjere su omjer crteža (128 : 106); kapica sjedi na kutu tako da obrub
     prelazi preko luka, a vrh i kuglica vise van. */
  width: 72px; height: 60px;
  z-index: 1;
  filter: drop-shadow(0 3px 6px rgb(0 0 0 / .2));
  pointer-events: none;
}

/* 🔑 Tko je u sustavu tražio manje animacije, dobiva ih manje — i to bez
   iznimke. Pahulje su ukras; kapica ostaje, jer ona miruje. */
@media (prefers-reduced-motion: reduce) {
  .hero.advent .hero-flake,
  .hero.advent .hero-flake > i { animation: none; }
  /* Bez gibanja pahulje nemaju što reći, a raspršene bijele točke po slici
     izgledaju kao prljavština. Zato se gase posve. */
  .hero.advent .hero-snow { display: none; }
}

@media (max-width: 640px) {
  .hero-wrap > .hero-hat { width: 56px; height: 45px; top: -12px; right: -8px; }
  .hero .hero-quote { display: none; }   /* na uskom ekranu prvo ide suština */
}

/* ── Popis slika u postavkama ─────────────────────────────────────────── */

.hero-thumbs { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 1rem; }

.hero-thumb {
  position: relative;
  display: block;
  width: 160px;
  border-radius: var(--shape-md);
  overflow: hidden;
  cursor: pointer;
  outline: 2px solid transparent;
  transition: outline-color .15s;
}
.hero-thumb img { display: block; width: 100%; height: auto; }

/* 🔑 KVAČICA POKRIVA SLIKU, a ne stoji ispod nje. Izmjereno u pregledniku:
   pri kliku se slika podigla za 16 px i ispod nje se ukazala bijela pruga.
   Razlog nije bio izgled nego pomicanje — `overflow: hidden` na labeli radi
   od nje spremnik koji se može pomaknuti, a nevidljivi checkbox je u toku
   stajao ISPOD slike. Dobivši fokus, preglednik ga je uredno „doveo u
   vidokrug" i pomaknuo sadržaj labele. Rastegnut preko cijele labele, nema
   ga se kamo dovoditi. */
.hero-thumb input {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

/* Odabir se vidi ODMAH na klik, prije spremanja — `:has()` čita stvarno
   stanje kvačice. Klasa `.on` sa servera ostaje za preglednike bez `:has()`:
   ondje se odabir vidi nakon spremanja, kao i dosad. */
.hero-thumb.on,
.hero-thumb:has(input:checked) { outline-color: var(--md-primary); }
.hero-thumb:has(input:not(:checked)) { outline-color: transparent; }

/* Kvačica se vidi samo kad je slika odabrana — neodabrana nema oznake, pa se
   razlika čita na prvi pogled, bez čitanja. */
.hero-thumb-mark {
  position: absolute; left: .4rem; top: .4rem;
  display: none;
  background: var(--md-primary); color: var(--md-on-primary);
  border-radius: 50%; padding: .15rem;
}
.hero-thumb.on .hero-thumb-mark { display: inline-flex; }
.hero-thumb:has(input:checked) .hero-thumb-mark { display: inline-flex; }
.hero-thumb:has(input:not(:checked)) .hero-thumb-mark { display: none; }

.hero-thumb-dim {
  position: absolute; left: .4rem; bottom: .3rem;
  color: #fff; text-shadow: 0 1px 2px rgb(0 0 0 / .6);
}

/* X je IZVAN labele (klik ne smije prebacivati kvačicu), ali U KUTU slike —
   zato okvir `.hero-thumb-box` nosi pozicioniranje. */
.hero-thumb-box { position: relative; }
.hero-thumb-x {
  position: absolute; top: .4rem; right: .4rem;
  width: 1.7rem; height: 1.7rem;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: rgb(0 0 0 / .55); color: #fff;
  cursor: pointer;
}
.hero-thumb-x:hover { background: var(--md-error); }
