/* ============================================================================
   discography.css — DYSKOGRAFIA: ekran ładowania + karuzela coverflow (js/disco-loader.js, js/clip-stage.js)
   Część rozbitego css/style.css — kolejność wczytywania (ta sama na każdej
   podstronie) trzyma kaskadę identyczną jak w jednym pliku.
   ========================================================================== */

/* ============================================================================
   EKRAN ŁADOWANIA — DYSKOGRAFIA
   Sekcja ciąga okładki ze Spotify i tła wideo z R2; do czasu, aż dojadą, cała
   scena stoi wygaszona, a na środku żyje ten ekran (sterowanie: DiscoLoader w
   js/discography.js). Bez JS (brak .js na <html>) ekran się NIE pokazuje, a
   scena jest normalnie widoczna — strona zostaje użyteczna.
   ========================================================================== */
.disco-loader{display:none}
html.js .disco-loader{
  display:flex;align-items:center;justify-content:center;
  position:fixed;inset:0;z-index:9998;background:#000;
  opacity:1;transition:opacity .52s ease;
}
html.js .disco-loader.is-hidden{opacity:0;pointer-events:none}
/* Jedyny element ekranu ładowania: cienki pasek postępu na środku. Bez napisów
   i bez animowanych ozdobników — ma być niewidoczny, kiedy sieć jest szybka. */
.disco-loader-bar{
  width:min(184px,46vw);height:2px;border-radius:2px;overflow:hidden;
  background:rgba(255,255,255,0.13);
}
.disco-loader-bar i{
  display:block;width:100%;height:100%;background:#fff;
  transform:scaleX(0);transform-origin:0 50%;transition:transform .35s ease-out;
}
/* Scena czeka wygaszona i wjeżdża dopiero z .disco-ready — użytkownik nie
   ogląda już doskakujących okładek, tylko gotowy kadr. */
html.js[data-page="discography"] .clip-stage{opacity:0}
html.js[data-page="discography"].disco-ready .clip-stage{opacity:1;transition:opacity .75s ease}
html.js[data-page="discography"].disco-ready .clip-stage-track{
  animation:disco-rise .85s cubic-bezier(.22,1,.36,1) both;
}
/* OSOBNE keyframes dla tytułu i etykiety: oba stoją na left:50% +
   transform:translateX(-50%), więc animacja MUSI nieść to przesunięcie w każdej
   klatce — inaczej (transform:none na końcu) napisy zjeżdżają w prawo o połowę
   własnej szerokości i przestają być wyśrodkowane. */
html.js[data-page="discography"].disco-ready .stage-title,
html.js[data-page="discography"].disco-ready .stage-kind{
  animation:disco-rise-center .85s cubic-bezier(.22,1,.36,1) both;
}
html.js[data-page="discography"].disco-ready .stage-title{animation-delay:.12s}
html.js[data-page="discography"].disco-ready .stage-kind{animation-delay:.18s}
@keyframes disco-rise{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}
@keyframes disco-rise-center{
  from{opacity:0;transform:translate(-50%,18px)}
  to{opacity:1;transform:translateX(-50%)}
}
@media (prefers-reduced-motion: reduce){
  html.js .disco-loader{transition:none}
  html.js[data-page="discography"].disco-ready .clip-stage{transition:none}
  html.js[data-page="discography"].disco-ready .clip-stage-track,
  html.js[data-page="discography"].disco-ready .stage-title,
  html.js[data-page="discography"].disco-ready .stage-kind{animation:none}
}

/* TELEDYSKI — teledysk aktywnego utworu jako PEŁNE tło całego slajdu (nie mała
   karta), przyciemnione 50% czernią + ziarno. .clip-stage to bezpośrednie dziecko
   .slide (position:absolute;inset:0, bez .main-wrap) — dzięki temu wideo faktycznie
   wypełnia cały ekran zamiast siedzieć w wąskiej ramce na tle reszty strony.
   Karuzela okładek: każdy utwór to jeden .clip-item, wszystkie leżą na sobie
   (position:absolute) i JS przesuwa je poziomo przez custom property --d
   (offset = index - aktywny), --s (skala) i --o (przezroczystość) — patrz
   setupClipStage w whyme.js. Aktywna okładka linkuje do Spotify; pod nią nazwa
   utworu, która na hover zamienia się (crossfade) na "Słuchaj". Nawigacja to
   dwie niewidoczne strefy kliknięcia na lewą/prawą połowę ekranu (.clip-click-zone-*),
   wyłączone (disabled) na granicy listy zamiast zawijać karuzelę w kółko. */
/* --stage-title-bottom: wspólny punkt odniesienia dla tytułu (na tej wysokości) i
   etykiety rodzaju TUŻ pod nim (ta sama wartość minus offset) — patrz .stage-title
   / .stage-kind niżej. Trzymanie tego w jednej zmiennej gwarantuje, że oba napisy
   przesuwają się razem i zawsze zachowują stały odstęp. */
/* touch-action:none (dawniej pan-y) — pan-y pozwalał przeglądarce przejmować
   pionowe gesty nad sceną (pointercancel w środku), a pionowy swipe ma teraz
   przewijać SEKCJE (patrz swipe na viewport w main.js), więc wszystkie gesty
   nad karuzelą zostają w Pointer Events. */
.clip-stage{position:absolute;inset:0;overflow:hidden;cursor:grab;touch-action:none;--stage-title-bottom:clamp(112px,16vh,188px)}
.clip-stage.is-dragging{cursor:grabbing;user-select:none}
/* isolation:isolate — trzyma <video> tła i .clip-stage-tint w jednej, stabilnej
   warstwie kompozytora. Bez tego niektóre silniki potrafią podczas scrolla
   zgubić kompozycję warstwy backdrop-filter na tincie i overlay na klatkę
   znika/miga, wracając dopiero po zakończeniu scrolla. */
.clip-stage-media{position:absolute;inset:0;overflow:hidden;isolation:isolate}
/* Każda pozycja ma WŁASNE tło (tworzone w JS) zamiast jednego, którego src/kolor
   jest podmieniany w locie — dzięki temu zmiana klipu to prawdziwy crossfade
   (opacity), a sąsiedni klip jest już wczytany, zanim użytkownik do niego dotrze,
   więc nie ma pustej/czarnej klatki w trakcie przenikania. */
.clip-bg-frame{
  position:absolute;inset:0;
  pointer-events:none;
  opacity:0;
  /* Lekki blur na tło — ma zostać "czyste", nie mydlić się w papkę, stąd mały promień. */
  filter:blur(3px);
  /* Długi, kinowy crossfade — stare tło gaśnie, nowe (już grające, patrz JS)
     wyłania się pod spodem, bez twardego cięcia. */
  transition:opacity 1.2s ease-in-out;
}
.clip-bg-frame.is-active{opacity:1}
/* Natywne <video> jako tło — object-fit:cover kadruje KAŻDE źródło (niezależnie od
   proporcji) do pełnego ekranu bez czarnych pasów, więc nie trzeba już żadnej
   matematyki 16:9 ani ręcznego zoomu jak przy starym odtwarzaczu YouTube. */
.clip-bg-video{
  position:absolute;top:0;left:0;
  width:100%;height:100%;
  object-fit:cover;
  z-index:-1;
  display:block;
  border:0;
}
/* CIEMNY OVERLAY — przyciemnia i lekko rozmywa ruchome tło pod spodem, żeby tekst
   nad nim został czytelny. transform:translateZ(0) + will-change wymuszają
   własną warstwę GPU: bez tego blur/nakładka potrafiły znikać/migać w trakcie
   scrolla (przeglądarka gubiła kompozycję warstwy backdrop-filter) i wracały
   dopiero po jego zakończeniu. -webkit-backdrop-filter jest wymagane osobno —
   WebKit (Safari/iOS) nie honoruje samego backdrop-filter bez prefiksu. */
.clip-stage-tint{
  position:absolute;inset:0;
  background:rgba(0,0,0,0.4);
  transform:translateZ(0);
  -webkit-transform:translateZ(0);
  will-change:transform,opacity;
  transition:background 1.2s ease-in-out;
}
/* Płaski kolor tła (album/EP bez klipu) to świadomy wybór artysty — ten sam
   overlay co pod wideo gasiłby go niemal do czerni, więc tu dostaje dużo
   lżejszą wersję, żeby kolor faktycznie było widać. */
.clip-stage-flat-bg .clip-stage-tint{background:rgba(0,0,0,0.15)}
.clip-stage-grain{
  position:absolute;inset:0;opacity:0.15;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Niewidoczne strefy kliknięcia — dokładnie połowa ekranu każda. <button> (nie
   div) daje za darmo klawiaturę/focus i to, że :disabled po prostu ignoruje
   kliknięcia na granicy listy (patrz zonePrev/zoneNext.disabled w whyme.js) —
   bez dodatkowego kodu na "nic się nie dzieje". z-index niżej niż .clip-stage-track,
   więc kliknięcie w samą (aktywną) okładkę trafia w nią, nie w strefę pod spodem. */
.clip-click-zone{
  position:absolute;top:0;bottom:0;width:50%;
  background:none;border:0;padding:0;margin:0;
  cursor:pointer;z-index:1;-webkit-tap-highlight-color:transparent;
}
.clip-click-zone-left{left:0}
.clip-click-zone-right{right:0}
.clip-click-zone:disabled{cursor:default}
.clip-click-zone:focus-visible{outline:2px solid rgba(255,255,255,0.4);outline-offset:-2px}

/* pointer-events:none tutaj i dziedziczy w dół na wszystkie .clip-item — inaczej
   ten kontener (rozciągnięty na całą scenę, inset:0) łapałby klik w dowolnym
   miejscu, gdzie żadne konkretne dziecko go nie przejęło, i strefy kliknięcia pod
   spodem nigdy by go nie dostały. Aktywna okładka odzyskuje auto inline'em w JS. */
.clip-stage-track{position:absolute;inset:0;z-index:2;pointer-events:none;perspective:1200px}
/* --clip-gap = JEDEN, jednolity odstęp między sąsiednimi okładkami w karuzeli.
   Mały (30vw) i CELOWO taki: bezpośredni sąsiedzi wchodzą w pełni na ekran jako
   czytelne podpowiedzi nawigacji (nie slivery przy krawędzi), a dalsi stoją tuż
   za krawędzią — patrz komentarz przy .clip-item niżej. */
:root{--clip-cover-size:clamp(180px,25vw,320px);--clip-gap:30vw}
.clip-item{
  position:absolute;top:50%;left:50%;
  width:var(--clip-cover-size);
  display:flex;flex-direction:column;align-items:center;
  /* POZYCJA W KARUZELI — jeden, JEDNOLITY odstęp (--clip-gap) na każdy krok --d
     (offset = index - aktywny). Każda okładka siedzi dokładnie o --d * --clip-gap
     od środka, więc przy zmianie klipu KAŻDA przejeżdża DOKŁADNIE jeden odstęp,
     tym samym tempem — to jest sedno ciągłego, bezszwowego przesuwu: nie ma
     "dziury" ani doskoku, bo wszystkie okładki suną równo względem siebie.
     Odstęp jest mały (30vw) świadomie: sąsiedzi (--d = ±1) wychodzą w pełni na
     ekran jako czytelne podpowiedzi, a dalsi (--d = ±2) stoją TUŻ za krawędzią,
     więc wjeżdżając na scenę nie muszą nadlatywać z daleka (stąd brak pustego
     pola i "chamskiego" doskoku na końcu, który był, gdy odstęp wynosił 54vw i
     dalsza okładka startowała spoza ekranu). Pozycja (ten translateX) jest
     oddzielona od przechyłu 3D (rotateY/scale na .clip-cover niżej), żeby origin
     przechyłu nie ciągnął okładki po osi X. Czas transition MUSI zgadzać się ze
     STAGE_ANIM_MS w whyme.js (od tej samej stałej liczony jest typewriter tytułu).
     BEZ filter/blur na tym elemencie: .clip-item ma transform-style:preserve-3d, a
     KAŻDY filter (nawet blur) wymusza spłaszczenie kontekstu 3D do "flat" — wtedy
     Chrome/Firefox/Windows renderują boczne okładki płasko w czasie przejazdu i
     "doskakują" do przechyłu dopiero po zdjęciu filtra (Safari jest łaskawszy, stąd
     "na Macu gra, na Windows nie"). Dlatego dawnego motion-blura (.is-moving) już
     tu nie ma — wolny, gładki suw nie potrzebuje rozmycia, a 3D jest spójne wszędzie. */
  transform:translate(-50%,-50%) translateX(calc(var(--d,0) * var(--clip-gap)));
  transform-style:preserve-3d;
  /* 1.3s (przyspieszone z 1.6s na życzenie — wciąż wolne i majestatyczne, tylko
     żwawsze) — MUSI zgadzać się ze STAGE_ANIM_MS w carousel.js. */
  transition:transform 1.3s cubic-bezier(0.65,0,0.35,1);
  will-change:transform;
}

/* PRZECHYŁ 3D okładek — perspektywa żyje na .clip-stage-track (wyżej), rotateY +
   scale na samej .clip-cover. transform-origin jest STAŁY (center center) dla
   WSZYSTKICH okładek i NIGDY nie jest animowany — i to jest naprawa "doskoku" kąta
   na końcu przejazdu. Wcześniej origin wędrował (aktywna: center, boczne: krawędź)
   i siedział w liście transition; animowanie transform-origin razem z rotateY na
   elemencie w kontekście 3D potrafiło się "zaciąć" — kąt trzymał 0 przez cały
   przejazd i dopiero na końcu skakał do docelowego przechyłu. Ze stałym originem
   rotateY jest po prostu częścią macierzy transform i interpoluje się gładko,
   JEDNOCZEŚNIE z translacją/skalą, przez całą drogę — okładki lądują w przechylonych
   pozycjach płynnie, bez skoku klatki. Aktywna (środkowa) jest płaska (rotateY 0),
   boczne przechylone o ±15deg wokół własnego środka (symetryczny coverflow): prawa
   -15deg, lewa +15deg (obie odchylają się "do środka" sceny). Czas przejścia jest
   DWUPOZIOMOWY: bazowy (krótki, .55s) obsługuje TYLKO hover (najechanie reaguje od
   razu), a podczas nawigacji nadpisuje go wolny, premium czas zgodny z .clip-item
   (patrz .is-navigating .clip-cover niżej) — rotateY, skala i jasność jadą w
   idealnym tandemie z pozycją w slocie. */
.clip-cover{
  position:relative;display:block;width:100%;aspect-ratio:1/1;
  border-radius:4px;overflow:hidden;
  box-shadow:0 30px 70px -14px rgba(0,0,0,0.75),0 14px 28px -10px rgba(0,0,0,0.6),0 0 0 1px rgba(255,255,255,0.08);
  opacity:var(--o,1);
  transform-origin:center center;
  /* translateZ(0.1px) — NIEZEROWY (to ważne!) — na początku KAŻDEGO transformu
     okładki (tu i w regułach bocznych/hover niżej) jest KLUCZOWE dla płynności
     przechyłu. Bez niego "rotateY(0deg)" środkowej okładki redukuje się do MACIERZY
     2D, a boczne "rotateY(±15deg)" to MACIERZE 3D; przejście 2D→3D przeglądarka
     interpoluje przez rozkład 3D tak, że kąt trzyma się blisko zera przez większość
     animacji i dopiero na końcu gwałtownie "doskakuje" do przechyłu (dokładnie ten
     trapez-skok na finiszu). UWAGA: translateZ(0) NIE działa — przeglądarka zwija
     zerowy Z z powrotem do 2D (sprawdzone). Dopiero niezerowe, ale niewidocznie małe
     0.1px (pod perspektywą rodzica to ~0.008% skali, poza percepcją) wymusza
     matrix3d na OBU końcach — rozkład jest spójny i rotateY interpoluje się równo od
     pierwszej do ostatniej klatki. Ta sama wartość Z we wszystkich stanach = Z jest
     stałe w czasie przejścia, więc każda klatka pozostaje 3D. */
  transform:translateZ(0.1px) rotateY(0deg) scale(var(--s,1));
  transition:transform .55s cubic-bezier(0.4,0,0.2,1),opacity .55s ease;
  will-change:transform,opacity;
}
/* Nawigacja (klasa .is-navigating na scenie, dodawana/zdejmowana w go() w JS) —
   okładka dostaje TEN SAM wolny, premium czas/easing co pozycja w slocie
   (.clip-item wyżej), żeby rotateY, skala i jasność szły dokładnie w tym samym
   tempie i tej samej krzywej, w jakim okładka jedzie na środek — jednym, gładkim
   ruchem od początku do końca. */
.clip-stage-track.is-navigating .clip-cover{
  transition:transform 1.3s cubic-bezier(0.65,0,0.35,1),opacity 1.3s cubic-bezier(0.65,0,0.35,1);
}
.clip-item.is-side-right .clip-cover{
  transform:translateZ(0.1px) rotateY(-15deg) scale(var(--s,1));
  cursor:pointer;
}
.clip-item.is-side-left .clip-cover{
  transform:translateZ(0.1px) rotateY(15deg) scale(var(--s,1));
  cursor:pointer;
}
/* Hover na boczną okładkę: WYPROSTOWUJE SIĘ do płaskiego 2D (rotateY 0, wokół tego
   samego, stałego środka co zawsze — animuje się tylko kąt/skala/jasność) i się
   rozjaśnia (opacity 0.8). Skala NIE rośnie na hover (zostaje --s) — subtelnie,
   premium. TYLKO poza nawigacją (:not(.is-navigating) na przodku) — inaczej, gdy
   user klika bezpośrednio w boczną okładkę (mysz zostaje nad nią przez cały
   przejazd), reguła hover na docelowym stanie (.is-active) podbijałaby skalę do
   1.2 zamiast spoczynkowej 1.1 przez cały czas dojazdu na środek. */
.clip-stage-track:not(.is-navigating) .clip-item.is-side-right .clip-cover:hover,
.clip-stage-track:not(.is-navigating) .clip-item.is-side-left .clip-cover:hover{
  opacity:0.8;
  transform:translateZ(0.1px) rotateY(0deg) scale(var(--s,1));
}
.clip-stage-track:not(.is-navigating) .clip-item.is-active .clip-cover:hover{
  transform:translateZ(0.1px) rotateY(0deg) scale(1.2);
}
.clip-cover-img{width:100%;height:100%;object-fit:cover;display:block;background:rgba(255,255,255,0.06)}
/* Overlay "OTWÓRZ" — tylko na aktywnej (środkowej) okładce. Jawne top/left/width/
   height (zamiast inset:0) + translateZ(0) + backface-visibility:hidden wymuszają
   własną warstwę GPU i trzymają overlay sztywno w kadrze rodzica podczas zmian
   macierzy 3D okładki. Bez tego, gdy okładka jechała na środek a kursor zostawał w
   centrum, ciemna nakładka + napis "OTWÓRZ" na moment wystrzeliwały do prawego
   dolnego rogu i wracały skokiem (błąd kompozycji przy przeliczaniu transformu 3D). */
.clip-cover-overlay{
  position:absolute;top:0;left:0;width:100%;height:100%;
  display:flex;justify-content:center;align-items:center;
  background:rgba(0,0,0,0);
  opacity:0;
  font-size:12px;font-weight:700;letter-spacing:3px;text-transform:uppercase;color:#fff;
  transition:opacity .3s ease,background .3s ease;
  pointer-events:none;
  transform:translateZ(0);
  -webkit-backface-visibility:hidden;
  backface-visibility:hidden;
}
.clip-stage-track:not(.is-navigating) .clip-item.is-active .clip-cover:hover .clip-cover-overlay{opacity:1;background:rgba(0,0,0,0.45)}

/* Tytuł ODŁĄCZONY od ruchomej okładki — jeden, stały, wyśrodkowany kontener pod
   stertą okładek (nie porusza się w bok razem z nimi). Podniesiony bliżej okładki
   (--stage-title-bottom, patrz .clip-stage) niż wcześniej. Zmiana tytułu to
   fade-out/fade-in (patrz .is-fading, przełączane w JS) + wpisywanie liter
   (typewriter), zsynchronizowane w JS z czasem dojazdu okładki na środek. */
.stage-title{
  position:absolute;bottom:var(--stage-title-bottom);left:50%;
  transform:translateX(-50%);
  height:18px;pointer-events:none;z-index:2;
  white-space:nowrap;
  font-size:13px;font-weight:600;letter-spacing:3px;text-transform:uppercase;color:rgba(255,255,255,0.82);
  opacity:1;
  transition:opacity .18s ease;
}
.stage-title.is-fading{opacity:0}

/* Etykieta typu wydawnictwa (SINGIEL/ALBUM/EP) — teraz TUŻ POD tytułem (a nie u
   samej góry ekranu jak wcześniej): bottom = ten sam punkt co tytuł, minus 24px,
   więc siedzi dokładnie pod nim, przesuwając się z nim razem (patrz
   CLIP_STAGE_ITEMS.kind w whyme.js). Rozmiar ok. 50% mniejszy od tytułu (7px vs
   13px) i mocno stonowana jasność (0.4) — ma być subtelna, premium sygnaturka, nie
   drugi tytuł. Ta sama animacja typewriter/fade co tytuł. */
.stage-kind{
  position:absolute;bottom:calc(var(--stage-title-bottom) - 24px);left:50%;
  transform:translateX(-50%);
  height:11px;pointer-events:none;z-index:2;
  white-space:nowrap;
  font-size:7px;font-weight:600;letter-spacing:2px;text-transform:uppercase;color:rgba(255,255,255,0.4);
  opacity:1;
  transition:opacity .18s ease;
}
.stage-kind.is-fading{opacity:0}

@media(max-width:640px){
  /* Na telefonie okładki są duże względem ekranu, więc odstęp jest większy niż na
     desktopie — sąsiedzi zaglądają zza krawędzi (a nie wchodzą w całości, bo
     zderzaliby się ze środkową). Pozycję liczy ta sama zmienna --clip-gap co na
     desktopie, więc wystarczy ją tu nadpisać — sam transform (translateX) jest
     wspólny, nie trzeba go przepisywać. */
  :root{--clip-cover-size:clamp(140px,46vw,220px);--clip-gap:44vw}
}
