/* ==========================================================================
   Mirza Tattoo - skelet
   Dizajn je namerno banalan. Ovde se za sada resava samo struktura,
   mehanika skrola i deck animacija galerije. Boje i tipografija dolaze posle.
   ========================================================================== */

/* --- Reset ---------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  /* Bez zaobljenih coskova bilo gde. Kad se odlucimo sta radimo sa coskovima,
     menja se samo ova jedna vrednost. */
  border-radius: var(--radius);
}

img {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

/* --- Tokeni --------------------------------------------------------------- */

:root {
  --radius: 0;

  /* Uzorkovano iz ugla logo.jpg - cisto crna. */
  --bg: #000000;
  --fg: #e8e6e3;
  /* Odnos prema crnoj pozadini mora da predje 4.5:1, inace tekst pada na
     proveri citljivosti. Na 0.3 je bilo oko 2.5:1 - lepo je izgledalo, ali se
     na telefonu po danu prakticno nije videlo. */
  --muted: rgb(232 230 227 / 0.72);
  --faint: rgb(232 230 227 / 0.62);
  --line: rgb(232 230 227 / 0.14);

  --header-h: 5rem;
  /* Jedan jedini razmak drzi celu stranicu: od leve ivice do naslova, od
     naslova do sadrzaja, i od sadrzaja do desne ivice. Sve tri su ista
     vrednost, pa se naslov cita kao da stoji u svom stupcu a ne kao da je
     naknadno prilepljen uz ivicu. */
  --gutter: 1.5rem;
  /* Sajt je na desktopu full width. Razmak od ivica ide kroz --gutter, ne kroz
     ogranicenje sirine - radovi su krupne crno-bele slike na crnom, a
     centrirana kolona sa praznim marginama sa strane bi ih stiskala.
     Duzina reda za citanje se i dalje drzi preko max-width u ch jedinicama
     (vidi .lede) - to je citljivost, ne layout. */
  --maxw: 100%;

  /* Traka rezervisana za vertikalni wordmark uz levu ivicu. Meri je main.js
     iz stvarne sirine naslova; ova vrednost je samo rezerva do tada. */
  --wm-lane: 200px;
  /* Odmak naslova od leve ivice je isti razmak kao svuda drugde. */
  --wm-inset: var(--gutter);

  /* Karta se meri po visini, ne po tome koliko ih stane u red. Sekcija mora
     da ostane niza od ekrana da bi drzala pod hederom, pa je visina fiksna
     a sirina je prati. Tako slike ostaju uspravne. */
  --card-h: 38vh;
  --card-w: 25vh;
  --card-overlap: 0.34;
}

@media (max-width: 767px) {
  :root {
    --card-h: 34vh;
    --card-w: 22vh;
    --gutter: 1.125rem;
    --header-h: 3.75rem;
  }
}

/* --- Osnova --------------------------------------------------------------- */

/* --- Lenis (glatki skrol) -------------------------------------------------
   scroll-behavior: smooth se ovde NE koristi. Lenis vodi skrol sam, pa bi dva
   glatka skrola radila jedan preko drugog i skakalo bi.
   Klase dodaje sam Lenis kad se pokrene; bez njega ostaje obican skrol. */

html.lenis,
html.lenis body {
  height: auto;
}

.lenis.lenis-smooth {
  scroll-behavior: auto !important;
}

.lenis.lenis-stopped {
  overflow: hidden;
}

.lenis.lenis-smooth [data-lenis-prevent] {
  overscroll-behavior: contain;
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 1rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /* Panel sistem radi na sticky elementima. Bilo kakav overflow na body ili
     html ubija sticky bez ijedne greske u konzoli. */
  overflow-x: clip;
}

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

h1,
h2,
h3 {
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

h1 {
  font-size: clamp(2.25rem, 7vw, 4.5rem);
}

h2 {
  font-size: clamp(1.75rem, 4vw, 2.75rem);
}

.lede {
  max-width: 46ch;
  color: var(--muted);
}

.eyebrow {
  font-size: 0.75rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--faint);
}

.btn {
  display: inline-block;
  padding: 0.9rem 2rem;
  border: 1px solid var(--fg);
  font-size: 0.8rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  transition: background-color 200ms ease, color 200ms ease;
}

.btn:hover {
  background: var(--fg);
  color: var(--bg);
}

/* --- VHS na hover ---------------------------------------------------------
   Isti jezik kao kvar na naslovu, samo stalan dok se stoji nad stavkom.

   Ovde nema slojeva kao kod naslova, pa razdvajanje kanala radi text-shadow:
   ista rec odstampana crveno malo udesno i plavo malo ulevo. Za ikone isto to
   radi drop-shadow, jer one nisu tekst pa na njih text-shadow ne deluje.

   Uvecanje ide preko scale, ne preko font-size: font-size bi promenio sirinu
   stavke i pomerio sve ostale u meniju svaki put kad se predje preko nje.

   steps(1, end) i ovde - kvar skace, ne klizi. */

.vhs-hover {
  display: inline-block;
  transition: transform 160ms ease;
}

.vhs-hover:hover,
.vhs-hover:focus-visible {
  animation: vhsHover 620ms steps(1, end) infinite;
}

@keyframes vhsHover {
  0% {
    transform: scale(1.14) translateX(0);
    text-shadow: 2px 0 #ff1e3c, -2px 0 #12e8ff;
  }
  22% {
    transform: scale(1.14) translateX(1px);
    text-shadow: -3px 0 #ff1e3c, 3px 0 #12e8ff;
  }
  44% {
    transform: scale(1.14) translateX(-1px);
    text-shadow: 1px 0 #ff1e3c, -1px 0 #12e8ff;
  }
  62% {
    transform: scale(1.14) translateX(0);
    text-shadow: none;
  }
  80% {
    transform: scale(1.14) translateX(2px);
    text-shadow: -2px 0 #ff1e3c, 2px 0 #12e8ff;
  }
  100% {
    transform: scale(1.14) translateX(0);
    text-shadow: 2px 0 #ff1e3c, -2px 0 #12e8ff;
  }
}

/* Ikone u futeru: isti pokret, ali kroz drop-shadow - on hvata oblik, dok
   text-shadow na SVG-u nema na sta da se primeni. */
.social a.vhs-hover:hover svg,
.social a.vhs-hover:focus-visible svg {
  animation: vhsHoverIcon 620ms steps(1, end) infinite;
}

@keyframes vhsHoverIcon {
  0% { filter: drop-shadow(2px 0 #ff1e3c) drop-shadow(-2px 0 #12e8ff); }
  22% { filter: drop-shadow(-3px 0 #ff1e3c) drop-shadow(3px 0 #12e8ff); }
  44% { filter: drop-shadow(1px 0 #ff1e3c) drop-shadow(-1px 0 #12e8ff); }
  62% { filter: none; }
  80% { filter: drop-shadow(-2px 0 #ff1e3c) drop-shadow(2px 0 #12e8ff); }
  100% { filter: drop-shadow(2px 0 #ff1e3c) drop-shadow(-2px 0 #12e8ff); }
}

/* Dugme se ne uvecava toliko - ono ima okvir, pa se svako rastezanje vidi i
   na njemu, ne samo na slovima. */
.btn.vhs-hover:hover,
.btn.vhs-hover:focus-visible {
  animation-name: vhsHoverBtn;
}

@keyframes vhsHoverBtn {
  0% { transform: scale(1.05) translateX(0); text-shadow: 2px 0 #ff1e3c, -2px 0 #12e8ff; }
  22% { transform: scale(1.05) translateX(1px); text-shadow: -3px 0 #ff1e3c, 3px 0 #12e8ff; }
  44% { transform: scale(1.05) translateX(-1px); text-shadow: 1px 0 #ff1e3c, -1px 0 #12e8ff; }
  62% { transform: scale(1.05) translateX(0); text-shadow: none; }
  80% { transform: scale(1.05) translateX(2px); text-shadow: -2px 0 #ff1e3c, 2px 0 #12e8ff; }
  100% { transform: scale(1.05) translateX(0); text-shadow: 2px 0 #ff1e3c, -2px 0 #12e8ff; }
}

/* Stavka trenutne stranice nosi kvar stalno, ne samo pod kursorom - tako se
   vidi gde si i kad mis nije nigde blizu.
   Bez uvecanja: ono je odgovor na kursor, a ovo je stanje. Uvecana stavka bi
   pomerala susedne i stalno gurala meni. */
.nav a[aria-current="page"] {
  color: var(--fg);
  animation: vhsActive 900ms steps(1, end) infinite;
}

@keyframes vhsActive {
  0%, 58% { text-shadow: none; }
  62% { text-shadow: 2px 0 #ff1e3c, -2px 0 #12e8ff; }
  70% { text-shadow: -3px 0 #ff1e3c, 3px 0 #12e8ff; }
  78% { text-shadow: none; }
  86% { text-shadow: 2px 0 #ff1e3c, -2px 0 #12e8ff; }
  100% { text-shadow: none; }
}

@media (prefers-reduced-motion: reduce) {
  .vhs-hover:hover,
  .vhs-hover:focus-visible,
  .social a.vhs-hover:hover svg,
  .nav a[aria-current="page"] {
    animation: none;
  }
}

/* --- Ulazak hedera --------------------------------------------------------
   Stavke menija uklizu s leve ivice hedera i prelaze ceo njegov put do svog
   mesta. Prva stane ona krajnje desna, pa se ostale zaustavljaju iza nje -
   kao da se sudaraju i slazu jedna uz drugu.

   Svaka ima svoj put, jer svaka zavrsava na drugom mestu. Taj put meri
   main.js i upisuje ga u --nx; u CSS-u se ne moze izracunati.

   Znak se ne pomera nego se pali, i traje tacno onoliko koliko i poslednja
   stavka - da sve bude gotovo u istom trenutku. Trajanje upisuje main.js u
   --brand-ms, posto zavisi od broja stavki.

   Dok merenje ne stigne, sve je ugaseno: bez toga bi meni bljesnuo na svom
   mestu pa tek onda odleteo levo da se vrati. */

.site-header:not(.is-ready) .nav a,
.site-header:not(.is-ready) .brand {
  opacity: 0;
}

.site-header.is-entering .nav a {
  animation: navSlide 700ms cubic-bezier(0.34, 1.45, 0.64, 1) both;
  animation-delay: calc(var(--nd, 0) * 1ms);
}

@keyframes navSlide {
  0% {
    opacity: 0;
    transform: translateX(calc(var(--nx, 0px) * -1));
  }
  18% {
    opacity: 1;
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}

.site-header.is-entering .brand {
  animation: brandIn var(--brand-ms, 880ms) ease both;
}

@keyframes brandIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .site-header.is-entering .nav a,
  .site-header.is-entering .brand {
    animation: none;
  }
}

/* --- Header --------------------------------------------------------------- */

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: var(--bg);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.9rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
}

.brand img {
  width: 2.25rem;
  height: 2.25rem;
  object-fit: contain;
}

.nav {
  display: flex;
  gap: 2rem;
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

.nav a {
  color: var(--muted);
  transition: color 200ms ease;
}

.nav a:hover,
.nav a[aria-current="page"] {
  color: var(--fg);
}

/* Hamburger. Tri crte, koje se pri otvaranju sklope u krst - ista tri
   elementa, bez zamene ikone, pa se prelaz moze animirati. */
.nav-toggle {
  display: none;
  position: relative;
  width: 26px;
  height: 18px;
  flex: none;
}

.nav-toggle span {
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--fg);
  transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1), opacity 160ms ease;
}

.nav-toggle span:nth-child(1) { top: 0; }
.nav-toggle span:nth-child(2) { top: 8px; }
.nav-toggle span:nth-child(3) { top: 16px; }

.nav-toggle.is-open span:nth-child(1) {
  transform: translateY(8px) rotate(45deg);
}

.nav-toggle.is-open span:nth-child(2) {
  opacity: 0;
}

.nav-toggle.is-open span:nth-child(3) {
  transform: translateY(-8px) rotate(-45deg);
}

@media (prefers-reduced-motion: reduce) {
  .nav-toggle span {
    transition: none;
  }
}

@media (max-width: 767px) {
  /* Ime u hederu odlazi, ostaje samo znak. Traka je odnela deo sirine pa se
     "Mirza Tattoo" prelamalo u dva reda, a ionako je suvisno - isto ime stoji
     uspravno preko cele visine, dva puta veci od svega ostalog na ekranu. */
  .brand span {
    display: none;
  }

  .nav-toggle {
    display: block;
  }

  .nav {
    position: fixed;
    inset: var(--header-h) 0 auto 0;
    flex-direction: column;
    align-items: center;
    gap: 0;
    background: var(--bg);
    border-bottom: 1px solid var(--line);
    display: none;
  }

  .nav.is-open {
    display: flex;
  }

  .nav a {
    padding: 1.125rem var(--gutter);
    border-top: 1px solid var(--line);
    /* Krupnije nego u traci na desktopu - ovde je meni ceo ekran, a ne red
       sitnih stavki u uglu. */
    font-size: 1.5rem;
    letter-spacing: 0.12em;
    color: var(--fg);
    text-align: center;
    width: 100%;
  }

  /* Ulazak: slova se pale nasumicnim redom unutar stavke, a stavke idu jedna
     za drugom odozgo nadole. Isti postupak kao kod uspravnog naslova - samo
     sto ovde svaka stavka ima i svoje mesto u redu.
     Kasnjenja racuna main.js i upisuje ih svakom slovu u --d. */
  .nav.is-open .ch {
    animation: navChar 620ms ease-out both;
    animation-delay: calc(var(--d, 0) * 1ms);
  }

  @keyframes navChar {
    from {
      opacity: 0;
      transform: translateY(0.35em);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  .nav .ch {
    display: inline-block;
  }

  .nav .ch.space {
    width: 0.32em;
  }
}

@media (prefers-reduced-motion: reduce) {
  .nav.is-open .ch {
    animation: none;
  }
}

/* --- Panel sistem ---------------------------------------------------------
   Svaka sekcija drzi pod hederom dok sledeca ne naidje odozdo i ne prekrije
   je. Efekat "ona iznad se podvlaci pod novu" je samo sticky + rastuci
   z-index + neprozirna pozadina na svakoj sekciji.

   Pozadina je obavezna. Panel bez nje je providan i onaj ispod se vidi kroz
   njega umesto da bude prekriven.

   `is-unpinned` skida sticky sa panela koji je visi od raspolozivog prostora.
   Takav panel bi se zaglavio vrhom pod hederom i dno mu se nikad ne bi videlo.
   Samo JS moze da zna koji su to. */

/* Pozadina ide od ivice do ivice, sadrzaj ostaje uvucen za sirinu trake.
   Negativna margina vraca kutiju na levu ivicu ekrana, padding istog iznosa
   gura sadrzaj nazad. Bez toga sekcija pocinje tek od trake, pa se pri
   penjanju vidi kao pravougaonik cija leva ivica sece usred naslova.

   Samo na stranici koja naslov i ima. Traka postoji kao vrednost i tamo gde
   naslova nema, pa bi bezuslovno pravilo pomerilo sadrzaj ostalih stranica
   za sirinu necega sto se na njima ne prikazuje. */
/* Samo iznad telefona. Na uskom ekranu naslov nije uspravan sa strane nego
   stoji vodoravno u toku, pa nema trake ni cega da se zaobilazi. */
@media (min-width: 1024px) {
  body:has(.wordmark) .panel,
  body:has(.wordmark) .site-footer {
    margin-left: calc(var(--wm-lane) * -1);
    padding-left: var(--wm-lane);
  }
}

/* Linija krece tek iza trake s naslovom. Pozadina sekcije ide ispod naslova i
   to je u redu - crno na crnom se ne vidi. Linija se vidi, pa bi presecala
   slova popreko i citala se kao da ih je neko precrtao. */
@media (min-width: 1024px) {
  body:has(.wordmark) .panel::before,
  body:has(.wordmark) .site-footer::before {
    left: var(--wm-lane);
  }
}

.panel {
  position: sticky;
  /* Piksel vise nego sto heder zauzima. Sekcija nosi liniju na gornjoj ivici;
     kad se do kraja zakaci, ta linija se podvuce pod heder i nestane. Bez tog
     piksela bi ostala da stoji ispod hedera dokle god sekcija drzi, i citala
     bi se kao okvir hedera - a njega smo namerno skinuli. */
  top: calc(var(--header-h) - 1px);
  background: var(--bg);
}

/* Linija na gornjoj ivici. Bas ta ivica se penje preko prethodne sekcije, pa
   jedna linija radi dva posla: oznacava gde sekcija pocinje i cini prelaz
   vidljivim. U crnom na crnom se bez nje ne vidi da se sta uopste dogodilo.

   Slabi ka krajevima umesto da ide od ivice do ivice. Puna linija preko celog
   ekrana je pravilo u tabeli; ova se cita kao potez. */
.panel::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgb(232 230 227 / 0.38) 14%,
    rgb(232 230 227 / 0.38) 86%,
    transparent 100%
  );
}

.panel.is-unpinned {
  position: relative;
  top: auto;
}

/* Poslednji panel mora da bude iznad svih prethodnih. Bez ovoga bi ga
   prethodni, koji stize kasnije u izvornom redu, prekrio.
   Brojanje krece od 2: jedinica pripada vertikalnom naslovu, koji stoji iznad
   heroja ali ispod panela. */
.panel:nth-of-type(1) { z-index: 2; }
.panel:nth-of-type(2) { z-index: 3; }
.panel:nth-of-type(3) { z-index: 4; }
.panel:nth-of-type(4) { z-index: 5; }
.panel:nth-of-type(5) { z-index: 6; }
.panel:nth-of-type(6) { z-index: 7; }

/* Prostor da zakaceni panel odstoji neko vreme pre nego sto ga sledeci
   pregazi. Ta praznina se nikad ne vidi - zakaceni panel je pokriva ceo. */
.panel:not(.is-unpinned) + .panel {
  margin-top: 55vh;
}

.panel-inner {
  min-height: calc(100vh - var(--header-h));
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Deca se skupljaju na svoj sadrzaj umesto da se rastezu preko cele sirine.
     Otkad je sajt full width, dugme bez ovoga ide s kraja na kraj ekrana. */
  align-items: flex-start;
  padding-block: 4rem;
}

/* --- Ritam unutar sekcije -------------------------------------------------
   Razmaci nisu proizvoljni nego stepenasti: najmanji izmedju natpisa i naslova
   (oni pripadaju jedno drugom), veci od naslova do teksta, najveci do dugmeta.
   Tako se cita gde jedna misao prestaje a druga pocinje, bez ijedne linije. */

.panel-inner .eyebrow + h2 {
  margin-top: 0.9rem;
}

.panel-inner h2 + .lede,
.panel-inner h2 + .about-split {
  margin-top: 1.6rem;
}

.panel-inner .lede + .btn {
  margin-top: 2.75rem;
}

.panel-inner h2 {
  /* Krupnije nego podrazumevani h2 - na punoj sirini sekcije naslov od dva
     reda inace deluje sitno u odnosu na prazninu oko sebe. */
  font-size: clamp(2rem, 3.4vw, 3.5rem);
  line-height: 1.05;
}

.panel-inner .lede {
  font-size: clamp(1rem, 1.15vw, 1.2rem);
  max-width: 44ch;
}

/* --- Hero ----------------------------------------------------------------- */

/* Hero se drzi kao i paneli, samo pod samim vrhom umesto pod hederom - prvi
   panel se penje preko njega. Bez ovoga bi hero prosto odskrolao i prvi
   prelaz bi bio jedini koji nema efekat. */
.hero {
  position: sticky;
  top: 0;
  z-index: 0;
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding-top: var(--header-h);
  background: var(--bg);
}

/* Isti predah kao izmedju panela, da hero odstoji pre nego sto ga prvi
   panel pregazi. */
.hero + .panel {
  margin-top: 55vh;
}

/* Hero blok stoji na sredini prostora koji ostane pored naslova, ne uz njegov
   levi rub. Naslov je vec tezak vertikalni potez uz ivicu; tekst prislonjen uz
   njega bi se citao kao njegov nastavak umesto kao zasebna misao. */
.hero-inner {
  display: grid;
  gap: 2.25rem;
  justify-items: center;
  text-align: center;
}

/* Meri se po visini ekrana, ne po sirini. Hero mora da stane u jedan ekran,
   pa je visina ta koja je ogranicena; sirina je na ovom sajtu ionako slobodna. */
.hero-logo {
  width: auto;
  height: clamp(9rem, 30vh, 20rem);
  /* Sirina se poznaje unapred iz odnosa fajla (2048x1692), pa brauzer ostavi
     mesto pre nego sto slika stigne. Bez toga ceo hero poskoci. */
  aspect-ratio: 2048 / 1692;
  object-fit: contain;
}

.hero-inner .lede {
  /* Krupnije nego u ostatku sajta - ovo je jedini tekst u heroju. */
  font-size: clamp(1.125rem, 1.6vw, 1.75rem);
  line-height: 1.5;
  max-width: 34ch;
  color: var(--fg);
}

/* --- Typewriter -----------------------------------------------------------
   Tekst se kuca slovo po slovo. Visina bloka se rezervise unapred, preko
   nevidljive kopije celog teksta - bez toga bi hero rastao dok se kuca i
   gurao sve ispod sebe niz stranicu. */

/* Sakriven dok ga skripta ne pripremi. Tekst stoji upisan u HTML-u, pa bi se
   izmedju prvog iscrtavanja i trenutka kad skripta stigne video ceo - a onda
   bi nestao i poceo da se ispisuje iz pocetka. Klasu `typing` dodaje main.js
   cim isprazni sadrzaj, dakle pre nego sto se ista vidi.
   Bez skripte tekst ostaje sakriven, pa ga <noscript> u zaglavlju vraca. */
[data-typewriter]:not(.typing) {
  visibility: hidden;
}

.typing {
  position: relative;
}

.typing-ghost {
  visibility: hidden;
}

.typing-out {
  position: absolute;
  inset: 0;
}

/* Kursor stoji samo na pasusu koji se trenutno kuca, i na kraju ostaje na
   poslednjem. Vidljivost vodi main.js.
   Skriva se preko `visibility`, ne `display`: mesto koje zauzima mora da
   ostane, jer je po njemu odmerena i nevidljiva kopija koja drzi visinu. */
.typing-out .typing-caret {
  visibility: hidden;
}

.typing-caret {
  display: inline-block;
  width: 0.06em;
  height: 1em;
  margin-left: 0.08em;
  vertical-align: -0.1em;
  background: var(--fg);
  animation: caretBlink 1s steps(1, end) infinite;
}

/* Kursor prestaje da trepce dok se kuca - trepce samo kad se ceka. Tako je
   treptaj znak da je red gotov, a ne stalna smetnja. */
.typing.is-typing .typing-caret {
  animation: none;
}

@keyframes caretBlink {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .typing-caret {
    display: none;
  }
}

/* --- Vertikalni wordmark --------------------------------------------------
   Stoji zakucan uz levu ivicu i ostaje tu dok se sekcije smenjuju iza njega.

   Dobija svoju traku umesto da se osloni na praznu levu marginu. Margina
   zavisi od sirine ekrana, pa bi wordmark na jednoj sirini stajao lepo a na
   drugoj upao u tekst - a prelomna tacka na kojoj se to desava zavisi i od
   duzine reci i od broja kopija. Rezervisana traka to pitanje uklanja.

   Vrednosti su prepisane iz laba (lab-vertikala.html). Tamo se stimuju. */

/* Telo se pomera udesno za sirinu trake. Heder i padajuci meni su fiksirani,
   pa ih padding na telu ne pomera - njima se leva ivica pomera zasebno.
   Lightbox je takodje fiksiran, ali on namerno ostaje preko celog ekrana.

   Vazi na svim sirinama, i na telefonu. Naslov je jedini <h1> na stranici -
   sakriti ga na uskom ekranu znacilo bi da telefon dobija stranicu bez
   ijednog naslova. */
@media (min-width: 1024px) {
  body:has(.wordmark) {
    padding-left: var(--wm-lane);
  }
}

/* Heder NE ulazi u traku - ide preko cele sirine, da znak stoji uz samu levu
   ivicu isto kao sto poslednja stavka menija stoji uz desnu. Naslov mu ne
   smeta: pocinje tek ispod hedera, pa se nigde ne preklapaju. */

.wordmark {
  /* Velicinu racuna main.js tako da rec popuni skoro celu visinu ekrana.
     Ova vrednost je samo rezerva dok merenje ne stigne - bez nje bi naslov
     bljesnuo u pogresnoj velicini. */
  --wm-size: 9vh;
  /* Slova gotovo spojena. Uspravno slozena latinica stoji na bok, pa je razmak
     izmedju slova zapravo sirina svakog slova, u koju je uracunata i njegova
     bocna praznina. Zato je potrebna negativna vrednost da bi se dodirivala -
     na nuli ostaje vidljiv procep. */
  --wm-tracking: -0.06em;
  /* Kontura i razmak su u em, ne u px: velicina se menja sa visinom ekrana,
     pa bi fiksna kontura na velikom naslovu bila dlaka a na malom greda. */
  --wm-stroke: 0.02em;
  --wm-gap: 0.52em;
  /* Providnost ISPUNE. Kontura je uvek pune jacine - ona crta oblik, i cim
     oslabi, slovo prestane da ima ivicu. Dubina se dobija tako sto se ispuna
     povlaci, a obris ostaje. */
  --wm-fill: 0;

  position: fixed;
  /* Rezerva dok merenje ne stigne. Konacan polozaj upisuje main.js: kutiju
     pomera ulevo za prazan pojas koji unutar nje stoji pre slova, tako da u
     ravan sa znakom u hederu stanu SLOVA, a ne ivica kutije koju niko ne vidi. */
  left: var(--wm-inset);
  top: 50%;
  /* vertical-rl polaze slova jedno ispod drugog i okrece ih za 90 stepeni.
     rotate(180deg) preokrece smer citanja u odozdo nagore - jedini smer koji
     uz levu ivicu ne izgleda naopako.

     Pomeraj za pola visine hedera centrira naslov u prostoru ISPOD hedera, a
     ne u celom ekranu. Bez toga mu vrh ulazi pod heder cim naraste. */
  transform: translateY(calc(-50% + var(--header-h) / 2)) rotate(180deg);
  writing-mode: vertical-rl;
  /* Iznad svega osim hedera. Sekcije sada idu od ivice do ivice, pa bi naslov
     ispod njih bio prekriven cim prva naidje. Sudara nema jer je traka siroka
     taman toliko da u nju stane i razvlacenje kontura - sadrzaj sekcije pocinje
     tek iza njega. */
  z-index: 80;
  pointer-events: none;

  font-family: "M PLUS 1", "Segoe UI", sans-serif;
  font-size: var(--wm-size);
  font-weight: 900;
  letter-spacing: var(--wm-tracking);
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Prvi sloj stoji u toku i drzi mesto - od njega se racuna visina i sirina
   celog bloka. Ostali su izvuceni i naslagani preko njega.
   U miru sve leze tacno jedna na drugoj: razmak postoji samo dok traje
   deljenje, posle kog konture nestanu i ostane sama puna rec. */
.wordmark > span {
  display: block;
  color: rgb(232 230 227 / var(--wm-fill));
  -webkit-text-stroke: var(--wm-stroke) var(--fg);
  transform: translateX(0);
}

/* U miru se vidi samo prva kopija. Ostale zive samo dok traje deljenje, pa su
   inace ugasene - bez ovoga bi na ucitavanju bljesnule naslagane preko reci.

   Vazno: uslov je na redosledu, ne na .is-solid. Dok traje treperenje puna
   kopija skace kroz stog, pa .is-solid menja vlasnika po nekoliko puta - da
   je vidljivost vezana za njega, cela bi se gomila palila i gasila umesto da
   samo popuna prelazi s jedne na drugu. */
.wordmark > span:not(:first-child) {
  opacity: 0;
}

/* Neposredna deca, obavezno. Bez znaka > ovo hvata i pojedinacna slova unutar
   prve kopije, pa se sva naslazu u istu tacku - blok se onda meri kao jedno
   slovo i automatsko uklapanje velicine naduva naslov preko celog ekrana. */
.wordmark > span + span {
  position: absolute;
  inset: 0;
}

/* --- Naslov na telefonu i tabletu ------------------------------------------
   Uspravno postavljen naslov na uzem ekranu odnese trecinu sirine, a sadrzaju
   ostane stubac pretesan za citanje. Zato tamo pada u obican vodoravan naslov,
   na svoje uobicajeno mesto u toku.

   Granica je 1024px, ne 768: tablet ima istu nevolju kao telefon. Meni se i
   dalje sklapa u hamburger tek na 768 - to su dve razlicite odluke i namerno
   imaju razlicite granice.

   Element u HTML-u stoji kao prvo dete <main>-a, tacno tamo gde ga ovaj
   prikaz i trazi - nista ga ne premesta posle ucitavanja. Ranije jeste, i to
   je pomeralo sve ispod njega cim bi JS stigao. Na desktopu je svejedno gde
   je jer ga position:fixed ionako vadi iz toka.

   Ovde ostaje samo da se ponisti sve sto ga je drzalo uspravnim: fiksan
   polozaj, uspravni rezim pisanja i okretanje. Velicinu i polozaj koje je JS
   upisao u style takodje brise on sam. */

@media (max-width: 1023px) {
  /* Mere naslova stoje na :root jer ih ne cita samo on. Uvlak od hedera trazi
     i polozaj obojenih kopija, a ukupnu visinu bloka trazi prva sekcija, koja
     s naslovom deli prvi ekran. Da su brojevi upisani na svakom mestu posebno,
     jedan bi se pomerio pri sledecoj izmeni a ostali bi ostali gde su bili.

     Visina se RACUNA, ne meri. Rec stoji u jednom redu (white-space: nowrap
     iz osnovnog pravila) i line-height je 1, pa je visina reda tacno font-size
     - poznata jos pre prvog iscrtavanja. Da se merila iz JS-a, sekcije bi se
     prekrojile tek posle ucitavanja i stranica bi poskocila. */
  :root {
    --wm-line: clamp(2.25rem, 12vw, 3.75rem);
    --wm-top: calc(var(--header-h) + 3.5rem);
    --wm-gap-below: 3.5rem;
    --wm-block: calc(var(--wm-top) + var(--wm-line) + var(--wm-gap-below));
  }

  .wordmark {
    /* relative, ne static. Obojene kopije su `absolute` i trazile bi najblizeg
       pozicioniranog pretka - a to bi bio panel, pa bi odletele preko cele
       sekcije. Od kvara bi tada ostao samo trzaj glavne reci, bez boje. */
    position: relative;
    /* Obavezno. Osnovno pravilo daje `left` za uspravni polozaj; uz relative
       to postaje pomeraj udesno, pa naslov razvuce dokument za sirinu razmaka.
       Heder se onda razvuce s njim i hamburger izadje van ekrana. */
    left: auto;
    top: auto;
    writing-mode: horizontal-tb;
    transform: none;
    font-size: var(--wm-line);
    letter-spacing: -0.01em;
    text-align: left;
    /* Isti razmak od hedera na svakoj stranici. Naslov je prvo dete <main>-a,
       pa ovaj padding jedini odlucuje gde stoji - nijedna sekcija vise ne
       ucestvuje u tome.
       Prva vrednost je visina hedera (ispod njega naslov ne sme), druga je
       vazduh do njega. Ako treba nize ili vise, menja se samo taj drugi broj. */
    padding: var(--wm-top) var(--gutter) 0;
    /* Isti vazduh ispod naslova koliko ga ima iznad, da sedi u svom pojasu
       umesto da se lepi za sadrzaj ispod. */
    margin: 0 0 var(--wm-gap-below);
  }

  /* Razmak od vrha sada drzi naslov, pa ga sekcije ne smeju dodavati jos
     jednom - inace bi sadrzaj propao nadole za visinu hedera. */
  body:has(.wordmark) .hero,
  body:has(.wordmark) .gallery-section {
    padding-top: 0;
  }

  /* Ovde je ranije stajalo min-height: 0 za sve sekcije. Izgledalo je bezazleno
     jer se sekcija skupi na svoj sadrzaj - ali su razmaci od 55vh izmedju
     panela ostali, a kratka sekcija ih ne pokriva. Kroz te rupe se videla
     sekcija ispod: dugme iz heroja usred "O meni", traka sa slikama preko
     "Zakazivanja", i poslednja sekcija se nije mogla dovuci do kraja.

     Sada svaka sekcija drzi pun ekran, isto kao na desktopu. Prva ga deli s
     naslovom, pa joj se za njegovu visinu skracuje zahtev.

     svh, ne vh: na telefonu vh racuna ekran BEZ adresne trake, pa je sekcija
     uvek visa od onoga sto se stvarno vidi. svh uzima manju, sigurnu visinu -
     tacno jedan ekran onakav kakav korisnik gleda. */
  body:has(.wordmark) .hero {
    min-height: calc(100vh - var(--wm-block));
    min-height: calc(100svh - var(--wm-block));
  }

  body:has(.wordmark) .panel-inner {
    min-height: calc(100vh - var(--header-h));
    min-height: calc(100svh - var(--header-h));
  }

  /* Panel koji stoji odmah iza naslova - na "O meni" i "Kontakt" je to jedina
     sekcija na stranici. Gornji odmak mu ne treba: naslov ispod sebe vec
     ostavlja vazduh. */
  body:has(.wordmark) .wordmark + .panel > .panel-inner {
    min-height: calc(100vh - var(--wm-block));
    min-height: calc(100svh - var(--wm-block));
    padding-block: 0 4rem;
  }

  /* Kopije lezale su jedna na drugoj preko cele visine uspravnog naslova;
     vodoravno bi se razvukle u sirinu i pomerile sve ispod sebe.

     Polozaj se NE sme dati kao `inset: 0`. Apsolutni polozaj se racuna od
     padding kutije, a rec je od nje uvucena paddingom - kopije bi tako legle
     u gornji levi ugao, pod heder, umesto preko slova. Od kvara bi se onda
     video samo trzaj glavne reci, bez boje, a obojeni duhovi bi treperili
     preko hedera i otvorenog menija.

     Zato se isti uvlak nadoknadi ovde. Levo i desno umesto sirine: kutija se
     tako sama razvuce tacno preko reci, pa `clip-path` u kvaru sece slova, a
     ne prazninu iznad njih. */
  .wordmark > span + span {
    top: var(--wm-top);
    right: var(--gutter);
    bottom: auto;
    left: var(--gutter);
    width: auto;
  }
}

/* Puna kopija. Kontura se gasi jer bi preko punog slova podebljala bas nju i
   stog bi izgledao neravno. */
.wordmark > span.is-solid {
  color: var(--fg);
  -webkit-text-stroke-width: 0;
}

/* Kopije se povlace razlicitim debljinama. Kad su sve iste, stog je ravan i
   cita se kao greska u stampi; kad druga slabi za trecom, dobija se dubina i
   oko zna koja je napred. */
/* Svaka sledeca kopija je tanje konture i slabije ispune. Konture ostaju pune
   jacine na svima - one drze oblik. Povlaci se ispuna, pa se stog cita kao da
   se rec udaljava: prva jos ima telo, poslednja je skoro samo obris. */
.wordmark > span:nth-child(2) {
  --wm-stroke: 0.026em;
  --wm-fill: 0.5;
}

.wordmark > span:nth-child(3) {
  --wm-stroke: 0.016em;
  --wm-fill: 0.26;
}

.wordmark > span:nth-child(4) {
  --wm-stroke: 0.009em;
  --wm-fill: 0.1;
}

/* --- Ulazak u kadar --------------------------------------------------------
   Slova se pale jedno po jedno, nasumicnim redom - ne odozdo nagore niti bilo
   kojim drugim prepoznatljivim redosledom. Kad su sva tu, tek onda krece
   deljenje kopija.

   Redosled dodeljuje main.js, upisujuci svakom slovu svoje kasnjenje u --d.
   Slova su ugasena dok ih animacija ne upali; klasa se ne skida, jer animacija
   sa fill-om `both` zadrzava krajnje stanje i posle svog kraja. */

/* Samo prva kopija. Ostale su razlozene na slova iz istog razloga iz kog i
   ona - da bi se ispisale u istoj duzini - ali njihova slova ne ucestvuju u
   paljenju; one se pojavljuju tek sa deljenjem, i to kao celina. */
.wordmark > span:first-child .ch {
  opacity: 0;
}

.wordmark.is-entering > span:first-child .ch {
  animation: chFade 520ms ease-out both;
  animation-delay: calc(var(--d) * 1ms);
}

@keyframes chFade {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .wordmark > span:first-child .ch {
    opacity: 1;
  }

  .wordmark.is-entering > span:first-child .ch {
    animation: none;
  }
}

/* --- VHS glitch -----------------------------------------------------------
   Kvar kakav pravi istrosena traka: slika se rastavi na crveni i plavi kanal,
   oni se razidju u stranu, a preko svega prelete pomereni vodoravni iseci.

   Boju nose kopije koje ionako postoje. U miru su ugasene; za vreme kvara
   dobijaju punu ispunu u svojoj boji i `screen` mesanje, pa se tamo gde se
   preklapaju sabiraju nazad u belo - tacno kao razdvojeni kanali na ekranu.
   Bez `screen` bi gornja kopija prosto pokrila donju i boje se ne bi videle.

   steps(1, end) namerno: kvar ne sme da klizi izmedju stanja. Svaki kadar je
   skok, i to je jedina razlika izmedju kvara i pokreta. */

.wordmark.is-glitching > span:nth-child(2) {
  color: #ff1e3c;
  -webkit-text-stroke-width: 0;
  mix-blend-mode: screen;
  animation: vhsRed var(--vhs-ms, 300ms) steps(1, end) both;
}

.wordmark.is-glitching > span:nth-child(3) {
  color: #12e8ff;
  -webkit-text-stroke-width: 0;
  mix-blend-mode: screen;
  animation: vhsCyan var(--vhs-ms, 300ms) steps(1, end) both;
}

.wordmark.is-glitching > span:nth-child(4) {
  color: #f4f2ef;
  -webkit-text-stroke-width: 0;
  mix-blend-mode: screen;
  animation: vhsWhite var(--vhs-ms, 300ms) steps(1, end) both;
}

/* Crveni kanal bezi udesno, plavi ulevo - suprotni smerovi, inace se ne cita
   kao razdvajanje nego kao senka. */
@keyframes vhsRed {
  0%, 100% { opacity: 0; }
  8% { opacity: 0.9; transform: translateX(5px); clip-path: inset(0 0 0 0); }
  26% { opacity: 0.9; transform: translateX(9px); clip-path: inset(16% 0 47% 0); }
  44% { opacity: 0; }
  62% { opacity: 0.85; transform: translateX(-6px); clip-path: inset(54% 0 22% 0); }
  84% { opacity: 0.7; transform: translateX(3px); clip-path: inset(0 0 0 0); }
}

@keyframes vhsCyan {
  0%, 100% { opacity: 0; }
  8% { opacity: 0.9; transform: translateX(-6px); clip-path: inset(0 0 0 0); }
  26% { opacity: 0.9; transform: translateX(-10px); clip-path: inset(38% 0 25% 0); }
  44% { opacity: 0; }
  62% { opacity: 0.85; transform: translateX(7px); clip-path: inset(9% 0 66% 0); }
  84% { opacity: 0.7; transform: translateX(-4px); clip-path: inset(0 0 0 0); }
}

/* Beli isecak je pomerena traka - onaj deo slike koji je "preskocio". */
@keyframes vhsWhite {
  0%, 100% { opacity: 0; }
  18% { opacity: 0.85; transform: translateX(14px); clip-path: inset(28% 0 58% 0); }
  36% { opacity: 0; }
  58% { opacity: 0.9; transform: translateX(-12px); clip-path: inset(63% 0 14% 0); }
  76% { opacity: 0; }
}

/* Puna rec se ne iseca - ona je naslov stranice i mora da ostane citljiva.
   Dobija samo kratak trzaj u stranu, taman toliko da kvar zahvati i nju. */
.wordmark.is-glitching > span:first-child {
  animation: vhsMain var(--vhs-ms, 300ms) steps(1, end) both;
}

@keyframes vhsMain {
  0%, 44%, 100% { transform: translateX(0); }
  8% { transform: translateX(2px); }
  26% { transform: translateX(-3px); }
  62% { transform: translateX(3px); }
  84% { transform: translateX(-1px); }
}

/* Dok je padajuci meni otvoren, naslov miruje. On stoji tacno ispod menija,
   pa se svaki trzaj cita kao da se trese ceo meni - a meni je u tom trenutku
   ono sto korisnik gleda i po cemu cilja prstom.

   Gasi se animacija, ne sam kvar: tajmer u main.js i dalje radi svoje, samo
   se nista ne pomera dok je meni dole. Cim se zatvori, prvi sledeci kvar
   odigra normalno. */
body:has(.nav.is-open) .wordmark.is-glitching > span {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .wordmark.is-glitching > span {
    animation: none;
  }
}



/* --- Deck galerija --------------------------------------------------------
   Traka karata koja se deli kao spil: ceo snop uleti spolja s leve strane,
   pa se rasiri udesno karta po karta. Kad se podeli, traka krece u spori
   beskonacni drift.

   Slike stoje ravno. Nagiba nema. */

.deck-section {
  /* Kopija tekst blokova ulece spolja, pa mora da bude stegnuto bocno.
     Vertikalno ostaje otvoreno, da karte mogu da rastu iz trake na hover. */
  overflow-x: clip;
  overflow-y: visible;
}

/* Traka mora da drzi karte koje cekaju levo i desno, ali ne sme da sece po
   visini, inace se karta koja naraste na hover preseca po pola.
   `overflow: hidden` na jednoj osi prisiljava skrol na drugoj, a `clip-path`
   samo krije boju - traka od 300 karata bi i dalje sirila stranicu.
   `clip` je jedini koji radi oba: stegnuto popreko, otvoreno gore i dole. */
.deck-clip {
  overflow-x: clip;
  overflow-y: visible;
  cursor: grab;
  /* Vertikalni potezi se vracaju brauzeru da stranica i dalje skroluje, a
     stipanje da moze da se zumira. Horizontalni ostaju traci. */
  touch-action: pan-y pinch-zoom;
  /* Prostor da karta pod kursorom moze da naraste gore i dole. Maska ide po
     border-box-u, pa bi bez ovoga sve preko visine karte bilo pojedeno
     maskom i hover bi se secao po sredini. */
  padding-block: 5vh;

  /* Maske ovde vise nema. Bila je razvucena preko cele trake, pa je pomeranje
     iz feTurbulence secalo i sredinu - slike su izlazile isprugane i blede,
     a one su jedini razlog zbog kog stranica i postoji.
     Gasenje na krajevima sada radi preklop preko njih, vidi .deck-fade. */
}

.deck-clip:active {
  cursor: grabbing;
}

.deck-track {
  display: flex;
  user-select: none;
}

.deck-slot {
  position: relative;
  flex-shrink: 0;
  width: var(--card-w);
}

/* Svaka lezi preko repa prethodne, kao spil raskriljen po stolu. */
.deck-slot + .deck-slot {
  margin-left: calc(var(--card-w) * var(--card-overlap) * -1);
}

/* Ona pod kursorom izlazi ispod suseda i stoji iznad ostalih. */
.deck-slot:hover {
  z-index: 20;
}

/* Unutrasnji sloj nosi dolazak, da se nikad ne bije sa transformom trake. */
.deck-card {
  transition: transform 320ms cubic-bezier(0.16, 1, 0.3, 1);
}

.deck-slot:hover .deck-card {
  transform: scale(1.22);
}

/* Preko same slike kursor kaze da se otvara, ne da se vuce. Traka se prevlaci
   i dalje, i preko slike - ali `grab` bi bio jedini znak koji korisnik dobija,
   pa bi izgledalo da se slika moze samo prevlaciti. */
.deck-media {
  position: relative;
  height: var(--card-h);
  overflow: hidden;
  cursor: zoom-in;
}

.deck-clip:active .deck-media {
  cursor: grabbing;
}

.deck-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.deck-arrow {
  position: absolute;
  top: 50%;
  z-index: 20;
  transform: translateY(-50%);
  padding: 0.5rem 0.75rem;
  font-size: 2rem;
  line-height: 1;
  color: var(--faint);
  transition: color 200ms ease;
}

.deck-arrow:hover {
  color: var(--fg);
}

.deck-arrow.prev { left: 0.5rem; }
.deck-arrow.next { right: 0.5rem; }

/* --- Gasenje na krajevima trake -------------------------------------------
   Preklop u boji pozadine preko levog i desnog kraja, umesto maske na samim
   slikama. Slika ostaje puna svuda gde se vidi; gasi je samo ono sto lezi
   preko nje.

   Unutrasnja ivica preklopa je razderana, ne prava: gradijent je provucen kroz
   feTurbulence, pa se granica raspada. Pomeranje je slabo (scale 40) i deluje
   samo na uzak pojas uz ivicu - jaci broj bi poceo da grize u slike. */

.deck-fade {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 15;
  width: clamp(3rem, 9vw, 9rem);
  pointer-events: none;
  /* Pravougaonik je NAMERNO siri od vidnog polja maske: ide od -60 do 260, a
     vidi se samo 0 do 200.

     feDisplacementMap svaki piksel povlaci sa mesta do 20 jedinica u stranu.
     Kad je pravougaonik bio tacno po meri, na levoj ivici je uzorkovao izvan
     njega - a izvan njega nema nicega - pa je povlacio providnost i busio rupe
     u zasenjenju. Otud tanka isprekidana crta preko cele visine.

     Sa preklopom sa obe strane uvek ima materijala za uzorkovanje. Gradijent
     je vezan za korisnicki prostor (0 do 200), pa se preko tog preklopa
     produzava svojim krajnjim vrednostima: levo pun, desno providan. */
  --tear: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="600" viewBox="0 0 200 600" preserveAspectRatio="none"><defs><linearGradient id="g" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="200" y2="0"><stop offset="0" stop-color="%23fff" stop-opacity="1"/><stop offset="0.28" stop-color="%23fff" stop-opacity="1"/><stop offset="1" stop-color="%23fff" stop-opacity="0"/></linearGradient><filter id="f" x="-30%25" y="-30%25" width="160%25" height="160%25"><feTurbulence type="fractalNoise" baseFrequency="0.01 0.012" numOctaves="3" seed="9" result="n"/><feDisplacementMap in="SourceGraphic" in2="n" scale="40" xChannelSelector="R" yChannelSelector="G"/></filter></defs><rect x="-60" y="-60" width="320" height="720" fill="url(%23g)" filter="url(%23f)"/></svg>');
  -webkit-mask-image: var(--tear);
  mask-image: var(--tear);
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.deck-fade.left {
  left: 0;
  background: var(--bg);
}

.deck-fade.right {
  right: 0;
  background: var(--bg);
  transform: scaleX(-1);
}

/* --- Pad karata -----------------------------------------------------------
   Karte padaju odozgo, jedna po jedna, nasumicnim redom. Redosled i kasnjenje
   dodeljuje deck.js upisujuci svakoj svoje --d.

   Krivulja je ubrzavajuca pa naglo koci - pad, ne klizanje. Providnost se
   pali odmah na pocetku, da karta ne bude vidljiva gore iznad sekcije dok jos
   pada, nego da se pojavi tek kad ulazi u red. */

   Oznaku nosi samo onoliko karata koliko ih moze biti na ekranu - traka ih
   ima preko tri stotine, i animirati ih sve bi bilo uzalud placeno.
   Kad pad prodje, deck.js skida oznaku: animacija sa fill-om `both` drzi svoj
   zavrsni transform i posle kraja, pa bi zakljucala uvecanje na hover. */

.deck-card.will-drop {
  opacity: 0;
}

.deck-section.is-dropping .deck-card.will-drop {
  animation: cardDrop 720ms cubic-bezier(0.45, 0, 0.3, 1) both;
  animation-delay: calc(var(--d, 0) * 1ms);
}

@keyframes cardDrop {
  0% {
    opacity: 0;
    transform: translateY(-55vh);
  }
  22% {
    opacity: 1;
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .deck-card.will-drop {
    opacity: 1;
  }

  .deck-section.is-dropping .deck-card.will-drop {
    animation: none;
  }
}

/* --- Grid galerija (portfolio stranica) -----------------------------------
   Pravi grid: redovi se pune s leva na desno pa prelamaju. Nikakav masonry. */

.grid-gallery {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 2px;
  margin-top: 3rem;
}

/* Galerija nije panel - grid je visok koliko treba i ne sme da se kaci pod
   heder, jer mu se dno nikad ne bi videlo. Razmak od vrha nosi sama sekcija.
   U klasi je, ne u inline stilu: inline se ne moze pregaziti iz media query-ja,
   a na telefonu ovaj razmak treba da nestane. */
.gallery-section {
  padding-top: calc(var(--header-h) + 2rem);
  padding-bottom: 5rem;
}

/* Kad iznad grida nema naslova, nema ni cega da se odmakne. */
.wrap > .grid-gallery:first-child {
  margin-top: 0;
}

@media (max-width: 1279px) {
  .grid-gallery { grid-template-columns: repeat(4, 1fr); }
}

@media (max-width: 1023px) {
  .grid-gallery { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 599px) {
  .grid-gallery { grid-template-columns: repeat(2, 1fr); }
}

.grid-gallery button {
  display: block;
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 5;
}

/* --- Pomeranje slike unutar okvira ----------------------------------------
   Slika je visa od okvira za --overshoot sa svake strane i klizi kroz njega
   dok se skroluje. Zaostaje za stranicom, pa se okvir cita kao prozor kroz
   koji se gleda, a ne kao nalepnica.

   Pomeranje nosi omotac, uvecanje na hover nosi sama slika. Da su na istom
   elementu, morali bi da dele jedan transform - a hover ima prelaz od 400ms,
   pa bi se svaki pomeraj pri skrolu vukao kroz njega i kasnio. */

.grid-gallery .cell-media {
  /* Koliko je slika visa od okvira, sa svake strane. Ovo je zaliha iz koje se
     pomeranje crpi - main.js pomera nesto manje od ovoga, da pri krajnjem
     polozaju ivica okvira nikad ne ostane prazna.
     Veca vrednost znaci vise pokreta, ali i vise odsecene slike: okvir je
     fiksnog odnosa, pa sto je slika visa, to je uzi isecak koji se vidi. */
  --overshoot: 16%;
  position: absolute;
  left: 0;
  width: 100%;
  top: calc(var(--overshoot) * -1);
  height: calc(100% + var(--overshoot) * 2);
  transform: translate3d(0, var(--py, 0px), 0);
  will-change: transform;
}

/* --- Otkrivanje slika -----------------------------------------------------
   Slika se otkriva odozdo nagore, kroz masku - isti jezik kojim ulazi i
   vertikalni naslov.

   inset() meri uvlacenje od svake ivice. Uvlaci se gornja, od 100% do nule,
   pa vidljivi deo raste sa donje ivice navise.

   Maska ide na SLIKU, ne na polje oko nje. Polje se posmatra da bi se znalo
   kad je uslo u kadar, a clip-path na njemu bi ga sakrio i od posmatraca -
   IntersectionObserver racuna isecanje elementa i javlja da nista ne ulazi.
   Polje bi tako cekalo signal koji nikad ne stize.

   Oznaku dodaje main.js pri gradnji, jos pre prvog iscrtavanja - da slika ne
   bljesne cela pa se tek onda otkrije. Skida je kad otkrivanje prodje: maska
   sa fill-om `both` ostaje na snazi i posle kraja, a nema razloga da 101 slika
   nosi masku do kraja poseta. */

.grid-gallery button.will-reveal img {
  clip-path: inset(100% 0 0 0);
}

.grid-gallery button.is-revealed img {
  animation: cellReveal 760ms cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: calc(var(--d, 0) * 1ms);
}

@keyframes cellReveal {
  from {
    clip-path: inset(100% 0 0 0);
  }
  to {
    clip-path: inset(0 0 0 0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .grid-gallery button.will-reveal img {
    clip-path: none;
  }

  .grid-gallery button.is-revealed img {
    animation: none;
  }
}

.grid-gallery img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 400ms ease, opacity 300ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .grid-gallery .cell-media {
    transform: none;
  }
}

.grid-gallery button:hover img {
  transform: scale(1.06);
  opacity: 0.75;
}

/* --- Lightbox ------------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--gutter);
  background: rgb(0 0 0 / 0.94);
}

.lightbox.is-open {
  display: flex;
}

.lightbox img {
  max-width: 100%;
  max-height: calc(100vh - 8rem);
  object-fit: contain;
  /* Vodoravni potezi ostaju nama, za prelazak na susednu sliku. Uspravni i
     stipanje se vracaju brauzeru, da stranica moze da se zumira. */
  touch-action: pan-y pinch-zoom;
  cursor: grab;
  user-select: none;
  -webkit-user-drag: none;
  transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1), opacity 200ms ease;
}

/* Dok prst ili mis vuku, slika mora da prati bez zaostajanja - prelaz se gasi
   i vraca tek kad se pusti, za povratak na mesto. */
.lightbox.is-dragging img {
  transition: none;
  cursor: grabbing;
}

.lightbox-close,
.lightbox-nav {
  position: absolute;
  color: var(--muted);
  font-size: 2rem;
  line-height: 1;
  padding: 0.75rem;
  transition: color 200ms ease;
}

.lightbox-close:hover,
.lightbox-nav:hover {
  color: var(--fg);
}

.lightbox-close { top: 1rem; right: 1rem; }
.lightbox-nav.prev { left: 1rem; top: 50%; transform: translateY(-50%); }
.lightbox-nav.next { right: 1rem; top: 50%; transform: translateY(-50%); }

/* --- About ---------------------------------------------------------------- */

/* Fotografija ne diktira sirinu nego visinu: stoji tacno onoliko visoka koliko
   i uspravni naslov pored nje, pa se to dvoje cita kao jedan potez. Sirina je
   `auto` - prati odnos same slike.
   --wm-h upisuje main.js. Na telefonu ga nema (naslov je tada vodoravan), pa
   slika pada na svoj odnos i punu sirinu. */

.about-split {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 3rem;
  align-items: start;
}

.about-photo {
  height: var(--wm-h, auto);
  width: auto;
  max-width: 100%;
  object-fit: cover;
}

.about-text .lede + .lede {
  margin-top: 1.5rem;
}

/* --- Ocene i pratioci -----------------------------------------------------
   Cekaju da se tekst otkuca pa se pojave. Klasu dodaje main.js kad red za
   kucanje dodje do kraja; kasnjenje po stavci upisuje isto tamo. */

[data-after-typing] {
  opacity: 0;
}

[data-after-typing].is-shown .rating {
  animation: ratingIn 700ms cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: calc(var(--d, 0) * 1ms);
}

[data-after-typing].is-shown {
  opacity: 1;
}

@keyframes ratingIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.ratings {
  display: flex;
  flex-wrap: wrap;
  gap: 2.5rem;
  margin-top: 3.5rem;
}

.rating {
  display: grid;
  gap: 0.15rem;
}

.rating-score {
  font-size: 1.75rem;
  line-height: 1;
  color: var(--fg);
}

.rating-where {
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.rating-note {
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--faint);
}

@media (prefers-reduced-motion: reduce) {
  [data-after-typing] {
    opacity: 1;
  }

  [data-after-typing].is-shown .rating {
    animation: none;
  }
}

@media (max-width: 767px) {
  .about-split {
    grid-template-columns: 1fr;
    gap: 2rem;
  }

  .about-photo {
    height: auto;
    width: 100%;
    aspect-ratio: 3 / 4;
  }
}

/* --- Kontakt --------------------------------------------------------------
   Sve na sredini. Panel inace poravnava sadrzaj uz levu ivicu, pa ova klasa
   to izvrce - i po vodoravnoj osi i po poravnanju samog teksta. */

.is-centered {
  align-items: center;
  text-align: center;
}

.is-centered .lede {
  font-size: clamp(1.125rem, 1.6vw, 1.6rem);
  color: var(--fg);
  max-width: 32ch;
}

/* Kanali za zakazivanje. Auto-fit sam odlucuje koliko ih stane u red, pa se od
   cetiri u nizu do dva po redu prelazi bez ijedne prelomne tacke. */
.channels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 2.5rem 1rem;
  width: 100%;
  max-width: 58rem;
  margin: 3.5rem 0;
}

/* Bez okvira i bez linija izmedju - kanali stoje slobodno. Ranije su razmak od
   jednog piksela i pozadina u boji linije crtali resetku; sad je razmak pravi
   razmak, pa nema sta ni da se crta. */
.channel {
  display: grid;
  justify-items: center;
  gap: 0.6rem;
  padding: 0.5rem;
}

.channel svg {
  width: 26px;
  height: 26px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--muted);
  transition: color 220ms ease;
}

.channel:hover svg {
  color: var(--fg);
}

.channel svg .dot {
  fill: currentColor;
  stroke: none;
}

.channel-label {
  font-size: 0.65rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--faint);
}

.channel-value {
  font-size: 0.9rem;
  color: var(--fg);
  word-break: break-word;
}

/* Forma drzi svoju sirinu i stoji na sredini. Bez granice bi se polja na
   punoj sirini ekrana razvukla preko celog reda i izgledala kao tabela. */
.contact-form {
  width: 100%;
  max-width: 34rem;
  text-align: left;
}

.contact-form .btn {
  display: block;
  margin: 0.5rem auto 0;
}

.field {
  display: grid;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.field label {
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
}

.field input,
.field textarea {
  padding: 0.8rem 1rem;
  background: transparent;
  border: 1px solid var(--line);
  color: var(--fg);
  font: inherit;
}

.field input:focus,
.field textarea:focus {
  outline: 1px solid var(--fg);
  outline-offset: -1px;
}

/* --- Footer ---------------------------------------------------------------
   Poslednji panel drzi pod hederom kao i ostali, a futer se penje preko njega.
   Sticky drzi samo dok roditelj ima mesta ispod, a poslednji panel je
   poslednja stvar u <main> - zato razmaknica daje tacno visinu futera, a
   futer se povlaci nagore za isto toliko. Nista prazno ne ostaje.
   --footer-h se meri iz JS-a. Bez vrednosti je razmaknica nula i stranica se
   prosto zavrsi: promaseno merenje kosta zakacenje, nikad ne ostavlja rupu. */

.footer-dwell {
  height: var(--footer-h, 0px);
}

.site-footer {
  position: relative;
  /* Iznad vertikalnog naslova, ispod hedera. Futer zatvara stranicu, pa ono
     sto se penje preko poslednje sekcije mora da prekrije i naslov - inace
     naslov lezi preko futera i preko njegove linije. */
  z-index: 90;
  padding-block: 1.35rem;
  background: var(--bg);
  font-size: 0.8rem;
  color: var(--muted);
}

/* Futer se penje preko poslednje sekcije isto kao sto se sekcije penju jedna
   preko druge, pa nosi istu liniju na gornjoj ivici. Puna linija od ivice do
   ivice bi ovde bila jedini ravan potez na stranici. */
.site-footer::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgb(232 230 227 / 0.38) 14%,
    rgb(232 230 227 / 0.38) 86%,
    transparent 100%
  );
}

body:has(.footer-dwell) .site-footer {
  margin-top: calc(var(--footer-h, 0px) * -1);
}

.site-footer .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 1.5rem;
  flex-wrap: wrap;
}

/* Sva tri bloka se poravnavaju po sredini, ne po osnovnoj liniji teksta.
   Znak u kreditu je slika visine 18px, a tekst oko njega je 11px - po
   osnovnoj liniji bi jedno od to dvoje uvek stajalo krivo. */
.site-footer .wrap > * {
  margin: 0;
}

/* Poravnanje iznutra treba samo blokovima koji mesaju znak i tekst - kreditu
   i redu s ikonama. Obican pasus se izostavlja namerno: u flex kutiji svaki
   niz teksta oko <span>-a postaje zasebna stavka, a razmaci na njihovim
   krajevima se odbacuju. Tako je od "© 2026 Mirza Tattoo" ostajalo
   "©2026Mirza Tattoo". */
.site-footer .wrap > *:not(p) {
  display: flex;
  align-items: center;
}

/* Ikone umesto ispisanih imena. Crtane su konturom iste debljine i u istom
   okviru od 24 jedinice, pa stoje kao jedna porodica a ne kao tri preuzeta
   znaka. Boju nasledjuju iz currentColor, pa hover menja samo jedno mesto. */

.social {
  display: flex;
  align-items: center;
  gap: 1.25rem;
}

.social a {
  display: inline-flex;
  color: var(--muted);
  transition: color 200ms ease;
}

.social a:hover {
  color: var(--fg);
}

.social svg {
  width: 21px;
  height: 21px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Tacka na Instagramu je puna, ne obris - na ovoj velicini bi kruzic od
   poldrug piksela bio mrlja. */
.social svg .dot {
  fill: currentColor;
  stroke: none;
}

/* --- Omnium kredit --------------------------------------------------------
   Isti oblik kao na ostalim nasim sajtovima: rec "izrada" pa logo visine 18px.
   Visina je fiksna na svim projektima, da znak svuda izgleda isto. */

.footer-credit {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: lowercase;
  color: var(--faint);
}

.footer-credit a {
  display: inline-flex;
  opacity: 0.85;
  transition: opacity 300ms ease;
}

.footer-credit a:hover {
  opacity: 1;
}

.footer-credit img {
  display: block;
  height: 18px;
  width: auto;
}

/* --- Reduced motion ------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .deck-card,
  .grid-gallery img {
    transition: none;
  }
}
