/**
 * tilt-card.css — przechył 3D karty pod kursorem. Komponent wielokrotnego
 * użytku.
 *
 * Wymaga tokens.css. Mechanika ruchu siedzi w assets/js/tilt-card.js —
 * ten plik jest KONSTRUKCJĄ efektu, skrypt tylko podaje mu pozycję kursora.
 *
 * PODZIAŁ PRACY MIĘDZY CSS I JS. Skrypt nie zna ani jednego koloru, ani
 * jednej długości i ani jednego kąta. Zapisuje na karcie dwie liczby
 * bez jednostki — --dc-tilt-card-dx i --dc-tilt-card-dy, oba w zakresie
 * -1..1, gdzie 0 to środek karty — i przełącza atrybut stanu. Całą resztę
 * (perspektywę, kąt, skalę, czasy, barwę połysku) liczy CSS.
 *
 * Powód takiego cięcia: wartości projektowe mają być widoczne dla reguł
 * tego projektu — dla grepa po hex/rgb/px i dla kontroli nierozwiązanych
 * var(). Wartość wpisana w szablon literału w JS jest dla nich niewidzialna,
 * więc rozjechałaby się z paletą po cichu.
 *
 * TE DWIE ZMIENNE TO NIE POKRĘTŁA. Nie ma ich ustawiać ani sekcja, ani
 * strona — pisze je wyłącznie tilt-card.js, klatka po klatce. Komponent
 * NIE MA wariantów i nie ma czego nadpisywać: karta zachowuje się tak samo
 * wszędzie, gdzie stoi. Gdyby kiedyś miała się zachowywać inaczej, to jest
 * decyzja projektowa do podjęcia, nie pokrętło do dołożenia na zapas.
 *
 * ZNACZNIKI, których komponent oczekuje — rodzic z position: relative
 * i isolation: isolate (tak samo jak .dc-ring; patrz ring.css), atrybut
 * data-dc-tilt-card na karcie i jedna warstwa połysku w środku:
 *
 *   <div class="… dc-tilt-card" data-dc-tilt-card>
 *     <span class="dc-tilt-card__shine" aria-hidden="true"></span>
 *     …treść karty…
 *   </div>
 *
 * KARTA NIE JEST ELEMENTEM INTERAKTYWNYM. Bez tabindex, bez roli, bez
 * obsługi klawiatury — to dekoracja, która reaguje na kursor i nic poza
 * tym. Dlatego też cursor zostaje domyślny dla treści (default), a nie
 * pointer: nic tu nie da się kliknąć.
 *
 * CO JEST TU LICZBĄ, A NIE TOKENEM (reguła 17, punkt 4 — te trafienia
 * grepa są świadome):
 *
 *   1. `perspective(700px)`, kąt `18deg` i `scale3d(1.02)` to GEOMETRIA
 *      EFEKTU, nie wartości projektowe — dokładnie ta sama kategoria co
 *      kąty stożka w ring.css. Skala odstępów ani promieni nic o nich
 *      nie mówi i nie ma ich z czym scalić.
 *   2. Czasy 0.05s, .08s i .45s leżą poza skalą --dc-duration-*
 *      (najkrótszy token to .15s, najdłuższy .35s). Nie są scalone
 *      świadomie: 0.05s to nadążanie za kursorem — przy .15s karta
 *      wlecze się za wskaźnikiem — a .45s to powolny zjazd, który przy
 *      .35s przestaje być zjazdem. Krzywa zjazdu to już token:
 *      cubic-bezier(.2,.7,.2,1) z obu makiet to co do przecinka
 *      --dc-ease-out.
 *
 * SKĄD 18deg, A NIE 16deg. Makiety podają dwie wartości: home.html 18,
 * loyalty-card.html 16. To niespójność makiety, nie pokrętło — karta ma
 * zachowywać się identycznie na obu stronach. Wzięte 18, bo home.html jest
 * w CLAUDE.md wskazany jako źródło prawdy dla struktury i treści, i bo to
 * jedyne miejsce, w którym karta dziś stoi — wybór 16 zmieniłby zatwierdzony
 * stan sekcji loyalty-cta bez powodu leżącego po stronie projektu.
 */

/* ==========================================================================
   1. EFEKT — WYŁĄCZNIE DLA WSKAŹNIKA PRECYZYJNEGO I PRZY ZGODZIE NA RUCH

   Cały efekt mieszka w jednym zapytaniu medialnym, więc na dotyku i przy
   prefers-reduced-motion nie ma go w ogóle — nie ma czego wyłączać ani
   nadpisywać !important-em. To jest ta połowa reguły 12, którą da się
   załatwić w CSS; drugą połowę robi skrypt, nie podpinając w takim
   przypadku ani jednego nasłuchu.

   Karta w spoczynku NIE MA transformacji. To celowe: element bez
   transform nie zakłada warstwy kompozycji i rasteryzuje pismo dokładnie
   tak samo jak przed dołożeniem komponentu, więc dołożenie efektu nie
   zmienia wyglądu karty o żaden piksel. Przejście z transformacji do jej
   braku interpoluje się normalnie (brak transformacji to macierz
   jednostkowa), więc zjazd po opuszczeniu karty działa mimo to.
   ========================================================================== */

/* Wartości SPOCZYNKOWE obu zmiennych stanu, zadeklarowane bezwarunkowo.
   Zero znaczy „kursor w środku karty", czyli brak wychylenia i połysk
   dokładnie na osi. Deklaracja stoi tu z dwóch powodów: opisuje spoczynek
   w tym samym pliku, co ruch, i sprawia, że kontrola nierozwiązanych
   tokenów (reguła 17, punkt 5) nie zgłasza zmiennych, które poza chwilą
   ruchu myszy nigdzie nie są ustawiane. Sama deklaracja nic nie renderuje.

   To NIE są pokrętła — nadpisuje je wyłącznie tilt-card.js. */
.dc-tilt-card {
  --dc-tilt-card-dx: 0;
  --dc-tilt-card-dy: 0;
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .dc-tilt-card {
    /* Zjazd: dłuższy i z krzywą wyhamowania. Stan czynny niżej nadpisuje
       to na przejście nadążające. */
    transition: transform 0.45s var(--dc-ease-out);
  }

  /* Warstwa kompozycji zakładana na najeździe, a nie na stałe. Makieta
     trzyma will-change na karcie bez warunku, ale wtedy przeglądarka
     promuje warstwę także tam, gdzie kursor nigdy nie dotrze — a promocja
     potrafi sama zmienić rasteryzację pisma w spoczynku, czyli dokładnie
     to, czego ten komponent ma nie robić. :hover wypada w tej samej klatce
     co pierwsze mousemove, więc warstwa jest gotowa na czas. */
  .dc-tilt-card:hover {
    will-change: transform;
  }

  /* Kąt liczony z pozycji kursora: dx = -1 przy lewej krawędzi, +1 przy
     prawej. rotateX dostaje dy ze znakiem minus, żeby góra karty odchylała
     się OD kursora, a dolna ku niemu — czyli żeby karta „patrzyła"
     w stronę wskaźnika. */
  .dc-tilt-card[data-dc-tilt-active] {
    transform: perspective(700px)
      rotateX(calc(var(--dc-tilt-card-dy) * -18deg))
      rotateY(calc(var(--dc-tilt-card-dx) * 18deg))
      scale3d(1.02, 1.02, 1.02);
    transition: transform 0.05s ease;
  }

  .dc-tilt-card[data-dc-tilt-active] .dc-tilt-card__shine {
    opacity: 1;
  }
}

/* ==========================================================================
   2. POŁYSK
   Warstwa leży na treści karty i przepuszcza kursor. Deklarowana POZA
   zapytaniem medialnym, żeby jej geometria była jedna niezależnie od
   urządzenia — widoczna staje się dopiero w stanie czynnym (sekcja 1),
   a ten na dotyku i przy reduced-motion nigdy nie nastąpi.
   ========================================================================== */

/* Środek gradientu jedzie za kursorem: dx i dy z zakresu -1..1 przechodzą
   na 0–100% szerokości i wysokości karty.

   Barwa z color-mix() na --dc-text-on-dark, nie z literału. Makiety podają
   dwie: rgba(255,255,255,.18) i rgba(255,250,242,.18) — to ta sama biel
   przy tej samej alfie, różniąca się wyłącznie ciepłem, czyli dokładnie
   tym, co paleta i tak koduje w tym tokenie (#ffffff w motywie jasnym,
   #F4F1EA w ciemnym). Zapis odtwarza więc wartość z home.html co do setnej,
   a w motywie ciemnym daje jej cieplejszy odpowiednik — ta sama drobna
   różnica odcienia, którą zaakceptowano przy help-cta.

   border-radius: inherit, bez pokrętła — ta warstwa ZAWSZE leży na karcie,
   więc zawsze ma jej promień (uwaga do reguły 18: słowo kluczowe CSS-a
   nie przeszłoby przez var(), a tu nie ma czego konfigurować).

   opacity zamiast podmiany tła: makieta przełącza background między
   gradientem a `transparent` z przejściem .08s, ale takie przejście nigdy
   się nie wykonało — obrazu tła nie da się interpolować z kolorem, więc
   gradient znikał skokowo. opacity realizuje ten sam zamiar w tym samym
   czasie. Reguła 11 („przełączaj display, nie opacity") tu nie obowiązuje:
   jej powodem jest warstwa, która schowana dalej liczy animację, a ta
   warstwa nie animuje niczego — i display: none odebrałby jej przejście. */
.dc-tilt-card__shine {
  position: absolute;
  z-index: var(--dc-z-overlay);
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background-image: radial-gradient(
    circle at calc((var(--dc-tilt-card-dx) + 1) * 50%) calc((var(--dc-tilt-card-dy) + 1) * 50%),
    color-mix(in srgb, var(--dc-text-on-dark) 18%, transparent) 0%,
    transparent 60%
  );
  transition: opacity 0.08s ease;
}
