/* Panel Klienta - WYŁĄCZNIE layout mobilny, niezależnie od urządzenia. */

html { display: flex; justify-content: center; background: #E9EAEC; }
html[data-theme="dark"] { background: #0A0C0F; }

body.customer-body {
    width: 100%;
    max-width: 480px;
    min-height: 100vh;
    background: var(--color-bg);
}

.customer-main { padding: 1.25rem 1.1rem 6rem; }

.customer-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 1.1rem 0.5rem;
    /* Szklany efekt przy przewijaniu treści pod spodem - pasek czuje się
       jak osobna warstwa unosząca się nad zawartością, nie doklejony
       prostokąt. Sticky (nie fixed) - zostaje na górze podczas
       przewijania, ale nadal w normalnym przepływie dokumentu. */
    position: sticky;
    top: 0;
    z-index: 5;
    background: rgba(246, 247, 249, 0.72);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-bottom: 1px solid rgba(226, 230, 236, 0.6);
}
html[data-theme="dark"] .customer-topbar {
    background: rgba(20, 23, 28, 0.72);
    border-bottom-color: rgba(44, 49, 59, 0.6);
}
.customer-topbar .store-name { font-weight: 600; color: var(--color-text); }
.customer-topbar form { margin: 0; }
.customer-topbar button {
    background: none; border: none; color: var(--color-text-muted);
    font-size: 0.83rem; cursor: pointer; font-weight: 500;
}

/* Cyfrowa karta lojalnościowa - indygo (zaufanie/premium), nie bursztyn. */
.loyalty-card {
    background: linear-gradient(135deg, var(--color-card-gradient-start) 0%, var(--color-card-gradient-end) 100%);
    border-radius: var(--radius-lg);
    padding: var(--space-5) var(--space-5);
    color: #fff;
    /* Cień "elevated" (najmocniejszy poziom) zamiast --shadow-card - to
       najważniejszy, uniesiony element całego panelu klienta, powinien
       mieć wizualnie największy dystans od tła. Inset na dole imituje
       delikatny kontakt z podłożem (subtelne dociemnienie krawędzi). */
    box-shadow: var(--shadow-elevated), inset 0 -1px 0 rgba(0,0,0,0.06);
    position: relative;
    overflow: hidden;
    margin-bottom: var(--space-5);
}
/* Cienka linia światła na górnej krawędzi - jakby padało na kartę z góry,
   daje wrażenie fizycznej, uniesionej powierzchni zamiast płaskiej
   naklejki na tle. Nie sięga do samych rogów (8%-92%), żeby nie
   konkurowała z zaokrągleniem karty. */
.loyalty-card::after {
    content: "";
    position: absolute;
    top: 0;
    left: 8%;
    right: 8%;
    height: 1px;
    background: rgba(255,255,255,0.22);
    z-index: 1;
}
.loyalty-card.on-light::after { background: rgba(255,255,255,0.55); }
/* Jasne (pudrowe) palety karty - wybierane przez właściciela w Ustawieniach
   (patrz App\Support\CardPalette) - automatycznie przełączają tekst na
   ciemny, inaczej biały tekst byłby nieczytelny na jasnym tle. Właściciel
   wybiera tylko kolor, to przełączenie dzieje się samo. */
.loyalty-card.on-light { color: #3D2233; }
.loyalty-card.on-light .progress-track { background: rgba(61,34,51,0.15); }
.loyalty-card.on-light .progress-text strong { color: #6B3D0A; }
.loyalty-card.on-light .all-unlocked svg { stroke: #6B3D0A; }
/* Delikatna tekstura - cienkie linie diagonalne + miękkie plamy światła,
   żeby karta wyglądała bliżej materiału niż płaskiego ekranu. Czysto
   kosmetyczne, nad treścią karty (z-index niższy niż .store-label itd.,
   które mają z-index:1 przy paskach postępu). */
.loyalty-card::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image:
        radial-gradient(circle at 15% 20%, rgba(255,255,255,0.09) 0, transparent 42%),
        radial-gradient(circle at 85% 75%, rgba(255,255,255,0.07) 0, transparent 46%),
        repeating-linear-gradient(115deg, rgba(255,255,255,0.028) 0 2px, transparent 2px 15px);
    pointer-events: none;
}
/* Jasne palety - białe nakładki (myślane jako jasne akcenty NA CIEMNYM
   tle) są niemal niewidoczne na tle, które już samo jest jasne. Ten sam
   kolor bazowy (#3D2233) co reszta tekstu/ikon na .on-light, więc
   tekstura pozostaje spójna z resztą karty, tylko w odwróconej
   biegunowości (ciemne akcenty na jasnym tle zamiast jasnych na ciemnym). */
.loyalty-card.on-light::before {
    background-image:
        radial-gradient(circle at 15% 20%, rgba(61,34,51,0.07) 0, transparent 42%),
        radial-gradient(circle at 85% 75%, rgba(61,34,51,0.055) 0, transparent 46%),
        repeating-linear-gradient(115deg, rgba(61,34,51,0.035) 0 2px, transparent 2px 15px);
}
/* Elementy pozycjonowane (::before) malują się NAD zwykłą, niepozycjonowaną
   treścią niezależnie od kolejności w DOM - bez tego tekstura wylądowałaby
   wizualnie NAD tekstem karty zamiast pod nim. Wszystkie bezpośrednie
   dzieci dostają position:relative + z-index:1, żeby były w tej samej
   warstwie stackingu co ::before, ale wyżej. */
.loyalty-card > * { position: relative; z-index: 1; }

/* ===== Styl "Szklany" (glassmorphism) - opcjonalny, wybierany przez
   właściciela w Ustawieniach obok koloru palety, niezależnie od niego.
   Miękka poświata w rogu (element HTML, nie pseudo-element - ::before
   i ::after są już zajęte przez istniejącą teksturę/linię światła
   powyżej) + panel z rozmyciem owijający liczbę punktów, jakby była na
   osobnej, szklanej tafli nad resztą karty. */
.loyalty-card .card-glass-glow {
    position: absolute;
    top: -40%;
    right: -15%;
    width: 200px;
    height: 200px;
    background: radial-gradient(circle, rgba(255,255,255,0.18), transparent 70%);
    border-radius: 50%;
    z-index: 0;
    pointer-events: none;
}
.loyalty-card .points-glass-panel {
    background: rgba(255,255,255,0.12);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border: 1px solid rgba(255,255,255,0.2);
    border-radius: var(--radius-md);
    padding: 0.6rem 0.9rem;
    display: inline-block;
}
/* Jasne palety - poświata i panel biały na już jasnym tle byłyby prawie
   niewidoczne; przyciemniona wersja (czarna zamiast białej) daje ten
   sam efekt głębi, widoczny niezależnie od jasności bazowego koloru. */
.loyalty-card.on-light .card-glass-glow { background: radial-gradient(circle, rgba(61,34,51,0.10), transparent 70%); }
.loyalty-card.on-light .points-glass-panel { background: rgba(61,34,51,0.06); border-color: rgba(61,34,51,0.14); }

/* ===== Styl "Organiczny" (miękki kształt w tle, wzorzec z nowoczesnych
   aplikacji fintech) - dwa elementy HTML, duży w prawym górnym rogu w
   jaśniejszym odcieniu palety, mniejszy w lewym dolnym jako przezroczyste
   kółko. Kolor blob-a pochodzi z color-mix() na bazie WYBRANEJ palety
   (--color-card-gradient-end, już dostępna), więc automatycznie
   dopasowuje się do wszystkich 12 palet bez osobnego kodowania każdej. */
.loyalty-card .organic-blob-1 {
    position: absolute;
    width: 240px;
    height: 240px;
    background: color-mix(in srgb, var(--color-card-gradient-end) 55%, white);
    border-radius: 43% 57% 65% 35% / 45% 40% 60% 55%;
    top: -65px;
    right: -85px;
    opacity: 0.45;
    z-index: 0;
    pointer-events: none;
}
.loyalty-card .organic-blob-2 {
    position: absolute;
    width: 130px;
    height: 130px;
    background: rgba(255,255,255,0.10);
    border-radius: 50%;
    bottom: -45px;
    left: -35px;
    z-index: 0;
    pointer-events: none;
}
/* Jasne palety - jaśniejszy odcień już jasnej bazy byłby prawie
   niewidoczny (za mały kontrast z tłem karty); ciemniejszy odcień
   (mieszany z czernią zamiast bieli) daje ten sam czytelny kształt. */
.loyalty-card.on-light .organic-blob-1 { background: color-mix(in srgb, var(--color-card-gradient-end) 35%, black); opacity: 0.12; }
.loyalty-card.on-light .organic-blob-2 { background: rgba(61,34,51,0.06); }

.loyalty-card .store-label {
    font-size: 0.78rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    opacity: 0.75;
    font-weight: 500;
}
.loyalty-card .points-value {
    font-size: 2.8rem;
    font-weight: 800;
    margin: 0.35rem 0 0.1rem;
    line-height: 1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}
.loyalty-card .points-label { font-size: 0.88rem; opacity: 0.8; }
.loyalty-card .customer-name { margin-top: 1.4rem; font-weight: 500; opacity: 0.9; font-size: 0.92rem; }

/* Pasek postępu do najbliższej nagrody - JEDYNY złoty akcent na karcie,
   bo to jedyny moment, który faktycznie oznacza nagrodę. */
.progress-block { margin-top: 1rem; position: relative; z-index: 1; }
.progress-text {
    font-size: 0.8rem;
    margin-bottom: 0.4rem;
    display: flex;
    justify-content: space-between;
    gap: 0.5rem;
    opacity: 0.9;
}
.progress-text strong { font-weight: 600; white-space: nowrap; color: #F3D9A8; font-variant-numeric: tabular-nums; }
.progress-track {
    height: 6px;
    background: rgba(255,255,255,0.2);
    border-radius: 999px;
    overflow: hidden;
    position: relative;
}
/* Kamienie milowe (25/50/75%) na pasku postępu - drobne pionowe kreski,
   dają wrażenie odległości do pokonania zamiast surowego procentu.
   Pozycjonowane WZGLĘDEM CAŁEGO TRACKA (nie wypełnienia), więc zostają
   na miejscu niezależnie od tego, ile procent jest już wypełnione. */
.progress-milestone {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    background: rgba(255,255,255,0.55);
    z-index: 1;
}
.loyalty-card.on-light .progress-milestone { background: rgba(61,34,51,0.28); }

/* Odznaka passy aktywności na karcie klienta - dane już liczone i
   pokazywane właścicielowi (patrz Customer::activityStreakMonths()),
   dotąd nigdy niewidoczne dla samego klienta, który ją "zdobywa". */
.streak-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    background: rgba(255,255,255,0.15);
    padding: 0.2rem 0.55rem;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 700;
    margin-top: 0.5rem;
}
.streak-pill svg { width: 12px; height: 12px; flex-shrink: 0; }
.loyalty-card.on-light .streak-pill { background: rgba(61,34,51,0.12); }
/* Kolor akcentu paska postępu na karcie - reużywa aktualną paletę karty
   (--color-card-gradient-start, nadpisywane inline na .loyalty-card w
   customer/home.php), NIE stałe złoto - właściciel wybiera JEDNĄ paletę,
   ten pasek automatycznie dostaje spójny z nią kolor. Świadomie TYLKO
   ten jeden pasek (na karcie głównej) - katalog nagród (customer/rewards.php)
   zostaje bez zmian, złoto tam nadal oznacza wyłącznie odblokowaną nagrodę. */
.progress-fill {
    height: 100%;
    background: var(--color-card-gradient-start, var(--color-gold));
    border-radius: 999px;
    transition: width 0.4s ease;
}
.all-unlocked {
    margin-top: 1rem;
    font-size: 0.83rem;
    font-weight: 500;
    opacity: 0.95;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.all-unlocked svg { width: 16px; height: 16px; stroke: var(--color-gold); fill: none; }

/* Kupony jednorazowe (niezależne od punktów) - wyeksponowane osobno od
   karty głównej, bo to inny mechanizm (promocja z terminem ważności),
   nie katalog nagród za punkty. Styl kuponu sklepowego - kolorowy
   "grzbiet" z procentem + przerywana linia + treść. */
.coupons-section { margin-bottom: var(--space-5); }
.coupons-section-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-text-muted);
    margin-bottom: 0.5rem;
}
.coupon-card {
    display: flex;
    align-items: stretch;
    background: var(--color-bg-card);
    border-radius: var(--radius-md);
    overflow: hidden;
    box-shadow: var(--shadow-soft);
    margin-bottom: 0.6rem;
}
.coupon-card-left {
    background: var(--color-gold);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 72px;
    flex-shrink: 0;
    padding: 0.8rem 0.4rem;
}
.coupon-card-pct { font-size: 1.15rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.coupon-card-dots {
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    padding: 0.5rem 0;
}
.coupon-card-dots span { width: 4px; height: 4px; border-radius: 50%; background: var(--color-border); margin: 1px 0; }
.coupon-card-right { padding: 0.7rem 0.9rem; flex: 1; min-width: 0; }
.coupon-card-title { font-weight: 700; font-size: 0.86rem; }
.coupon-card-meta { font-size: 0.72rem; color: var(--color-text-muted); margin-top: 0.2rem; }
.coupon-card-meta strong { color: var(--color-text); font-variant-numeric: tabular-nums; letter-spacing: 0.03em; }

/* Przycisk "Pokaż QR" i modal z wygenerowanym kodem - realizacja
   kuponu przez skan zamiast dyktowania/wpisywania 8 znaków przy kasie
   (mobilny audyt układu, propozycja 3). */
.coupon-show-qr-btn {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    margin-top: 0.5rem;
    padding: 0.35rem 0.6rem;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg);
    color: var(--color-accent-text, var(--color-primary));
    font-size: 0.72rem;
    font-weight: 600;
    cursor: pointer;
}
.coupon-show-qr-btn svg { width: 14px; height: 14px; flex-shrink: 0; }

.coupon-qr-modal {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(20, 26, 41, 0.55);
    z-index: 50;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}
.coupon-qr-modal.open { display: flex; }
.coupon-qr-modal-inner {
    background: #fff;
    border-radius: var(--radius-lg);
    padding: 1.5rem;
    max-width: 300px;
    width: 100%;
    text-align: center;
    position: relative;
}
.coupon-qr-modal-close {
    position: absolute;
    top: 0.6rem;
    right: 0.6rem;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: none;
    background: var(--color-bg);
    color: var(--color-text-muted);
    font-size: 0.9rem;
    cursor: pointer;
}
#coupon-qr-code { margin: 0.5rem 0; }
#coupon-qr-code svg { width: 100%; height: auto; max-width: 200px; }
.coupon-qr-modal-code {
    font-family: monospace;
    font-size: 1.1rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    margin-top: 0.5rem;
}
.coupon-qr-modal-hint { font-size: 0.76rem; color: var(--color-text-muted); margin-top: 0.6rem; }

.qr-pass-card {
    border-radius: var(--radius-md);
    overflow: hidden;
    margin-bottom: var(--space-5);
    box-shadow: var(--shadow-card);
}
/* Nagłówek "cyfrowej przepustki" (styl Apple Wallet/Google Pay) - kolor
   z WYBRANEJ przez klienta palety karty (te same zmienne CSS custom
   property co .loyalty-card, ustawione jako inline style na WSPÓLNYM
   rodzicu .qr-pass-card, żeby zarówno nagłówek jak i dekoracyjny pasek
   w .qr-pass-body mogły z nich korzystać przez dziedziczenie).
   Kropki "●●●●" to czysto dekoracyjne nawiązanie do zasłoniętego numeru
   karty w cyfrowych portfelach, bez żadnego faktycznego znaczenia. */
.qr-pass-header {
    background: linear-gradient(135deg, var(--color-card-gradient-start), var(--color-card-gradient-end));
    color: #fff;
    padding: 0.65rem 1rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 0.76rem;
    font-weight: 700;
}
.qr-pass-header-store { text-transform: uppercase; letter-spacing: 0.03em; }
.qr-pass-header-dots { letter-spacing: 0.15em; opacity: 0.65; font-size: 0.68rem; }
.qr-pass-header.on-light { color: #3D2233; }
.qr-pass-body {
    background: var(--color-bg-card);
    text-align: center;
    padding: 1.25rem;
}
/* Pasek dekoracyjny imitujący kod kreskowy - czysto ozdobny, wzmacnia
   metaforę "biletu" bez żadnego ryzyka dla faktycznej skanowalności
   kodu QR nad nim (w przeciwieństwie do kolorowego tła POD samym
   kodem, którego świadomie unikamy - patrz #qr-code niżej). */
.qr-pass-strip {
    height: 8px;
    background: repeating-linear-gradient(90deg, var(--color-card-gradient-start) 0 6px, transparent 6px 9px);
    margin-top: 0.8rem;
    opacity: 0.55;
    border-radius: 2px;
}
.qr-pass-body .hint { color: var(--color-text-muted); font-size: 0.83rem; margin-top: 0.7rem; }
.qr-pass-body .qr-phone {
    font-size: 0.92rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--color-text);
    margin-top: 0.8rem;
}
.qr-pass-body img, .qr-pass-body svg { max-width: 200px; width: 100%; height: auto; }
/* Kod QR MUSI mieć jasne tło niezależnie od trybu ciemnego - inaczej
   traci czytelność/skanowalność, co jest ważniejsze niż spójność
   wizualna z resztą ciemnego motywu. */
#qr-code { background: #fff; padding: 0.75rem; border-radius: var(--radius-md); display: inline-block; position: relative; }
/* Placeholder w kształcie kodu QR, widoczny przez krótką chwilę zanim
   JS (qrcode-core.js) wygeneruje faktyczny kod - reużywa mechanizm
   shimmer już istniejący w app.css (.skeleton-block), dotąd używany
   wyłącznie w panelu właściciela. JS nadpisuje CAŁY innerHTML #qr-code
   przez qr.createSvgTag(), więc ten placeholder znika sam, bez
   potrzeby dodatkowego kodu do jego usunięcia. */
.qr-skeleton {
    width: 200px;
    height: 200px;
    max-width: 100%;
    background: linear-gradient(90deg, var(--color-border) 25%, #F0F1F4 37%, var(--color-border) 63%);
    background-size: 400% 100%;
    animation: skeleton-shimmer 1.4s ease infinite;
    border-radius: var(--radius-sm);
}
/* Delikatna pulsująca poświata wokół kodu QR - w słabo oświetlonym sklepie
   łatwo go przeoczyć, subtelny powtarzający się puls przyciąga wzrok
   kasjera bez agresywnej animacji. Promień = promień #qr-code (--radius-md)
   + 4px marginesu poświaty, żeby otaczała element proporcjonalnie, nie
   dowolna, osobna wartość. */
#qr-code::before {
    content: "";
    position: absolute;
    inset: -6px;
    border-radius: calc(var(--radius-md) + 4px);
    border: 2px solid var(--color-btn-bg, var(--color-primary));
    opacity: 0;
    animation: qr-pulse 2.2s ease-out infinite;
    pointer-events: none;
}
@keyframes qr-pulse {
    0% { opacity: 0.55; transform: scale(1); }
    100% { opacity: 0; transform: scale(1.1); }
}
@media (prefers-reduced-motion: reduce) {
    #qr-code::before { animation: none; display: none; }
}

/* Katalog nagród - złoto pojawia się TYLKO przy odblokowanej pozycji. */
.reward-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.7rem;
    padding: 0.85rem 1rem;
    border-radius: var(--radius-input);
    border: 1px solid var(--color-border);
    background: var(--color-bg-card);
    margin-bottom: 0.6rem;
    position: relative;
}
.reward-item.featured { border-color: var(--color-gold); }
.featured-ribbon {
    position: absolute;
    top: -9px;
    right: 12px;
    background: var(--color-gold);
    color: #fff;
    font-size: 0.62rem;
    font-weight: 700;
    padding: 0.12rem 0.55rem;
    border-radius: 999px;
}
.reward-item.unlocked { border-color: var(--color-gold); background: var(--color-gold-light); }
/* Delikatna ukośna faktura zamiast pełnego wyszarzenia zablokowanej
   nagrody - czytelny sygnał "niedostępne" widoczny zanim klient w ogóle
   przeczyta liczbę punktów, nie tylko po fakcie. Bardzo subtelna różnica
   dwóch odcieni tła karty (nie szarość na kolorze), żeby nie wyglądało
   na błąd renderowania. */
.reward-item.locked {
    background-image: repeating-linear-gradient(135deg, var(--color-bg-card) 0 8px, var(--color-bg) 8px 16px);
}

.reward-group-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-text-muted);
    margin: 1rem 0 0.5rem;
}
.reward-group-label:first-child { margin-top: 0; }

/* Centrum powiadomień - zwijany kontener zbierający wszystkie banery
   informacyjne (urodziny, wygasające punkty, ustaw hasło, potwierdź
   e-mail) w jedno miejsce zamiast osobnego stosu kart. Domyślnie
   zwinięty (propozycja 2, mobilny audyt układu) - klient widzi liczbę
   powiadomień, rozwija tylko jeśli chce, zamiast przewijać obok
   każdego z osobna zanim dotrze do reszty ekranu. Ten sam wzorzec
   akordeonu co Ustawienia właściciela i katalog Nagród. */
/* Baner z prośbą o zgodę marketingową na pulpicie - widoczny tylko dla
   klientów bez aktywnej zgody, którzy jeszcze go nie zamknęli (patrz
   Customer::dismissMarketingPrompt() - trwałe, nie tylko na sesję).
   Świadomie ZAWSZE widoczny (nie chowany w zwijanym "Centrum
   powiadomień" poniżej) - to jest prośba wymagająca świadomej decyzji
   klienta, nie tylko informacja do przejrzenia, więc zasługuje na
   większą widoczność niż reszta banerów informacyjnych. */
.marketing-prompt-banner {
    display: flex;
    gap: 0.7rem;
    background: var(--color-gold-light);
    border: 1px solid color-mix(in srgb, var(--color-gold) 25%, transparent);
    border-radius: var(--radius-md);
    padding: 0.9rem 1rem;
    margin-bottom: var(--space-4);
    position: relative;
}
.marketing-prompt-icon {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--color-gold) 15%, transparent);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-gold);
}
.marketing-prompt-icon svg { width: 17px; height: 17px; }
.marketing-prompt-text { flex: 1; padding-right: 1.2rem; }
.marketing-prompt-text strong { display: block; font-size: 0.85rem; margin-bottom: 0.2rem; color: var(--color-text); }
.marketing-prompt-text p { margin: 0; font-size: 0.78rem; color: var(--color-text-muted); line-height: 1.5; }
.marketing-prompt-actions { display: flex; gap: 0.6rem; margin-top: 0.65rem; }
.marketing-prompt-actions form { margin: 0; }
.marketing-prompt-yes {
    background: var(--color-gold);
    color: #fff;
    border: none;
    border-radius: 8px;
    padding: 0.4rem 0.8rem;
    font-size: 0.76rem;
    font-weight: 700;
    cursor: pointer;
    font-family: inherit;
}
.marketing-prompt-no {
    background: none;
    border: none;
    color: var(--color-text-muted);
    font-size: 0.76rem;
    font-weight: 600;
    text-decoration: underline;
    cursor: pointer;
    font-family: inherit;
    padding: 0.4rem 0;
}
.marketing-prompt-close-form { position: absolute; top: 0.55rem; right: 0.6rem; margin: 0; }
.marketing-prompt-close {
    background: none;
    border: none;
    width: 22px;
    height: 22px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-text-muted);
    font-size: 0.85rem;
    cursor: pointer;
    border-radius: 50%;
}
.marketing-prompt-close:hover { background: rgba(0,0,0,0.06); }

.notification-center {
    background: var(--color-bg-card);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-5);
    overflow: hidden;
}
.notification-center-header {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.8rem 1rem;
    cursor: pointer;
}
.notification-bell { display: flex; color: var(--color-text-muted); flex-shrink: 0; }
.notification-bell svg { width: 18px; height: 18px; }
.notification-center-title { font-weight: 600; font-size: 0.88rem; flex: 1; }
.notification-count-badge {
    background: var(--color-danger);
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    border-radius: 999px;
    min-width: 18px;
    height: 18px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 0.35rem;
}
.notification-chevron { color: var(--color-text-muted); transition: transform 0.2s ease; flex-shrink: 0; display: flex; }
.notification-chevron svg { width: 16px; height: 16px; }
.notification-center.open .notification-chevron { transform: rotate(180deg); }
.notification-center-body {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.28s ease;
}
.notification-center.open .notification-center-body { max-height: 900px; }
.notification-center-body-inner {
    padding: 0 1rem 1rem;
    border-top: 1px solid var(--color-border);
}
.notification-center-body-inner .birthday-banner,
.notification-center-body-inner .expiry-banner,
.notification-center-body-inner .account-prompt {
    margin: 0.8rem 0 0;
}

.birthday-banner {
    background: linear-gradient(135deg, #FCE4EC, #FFF3E0);
    border: 1px solid #F4A6C0;
    border-radius: var(--radius-md);
    padding: 1rem;
    display: flex;
    align-items: center;
    gap: 0.7rem;
    font-size: 0.85rem;
    margin-bottom: 1rem;
    color: var(--color-text);
}
.birthday-banner .icon { font-size: 1.6rem; flex-shrink: 0; }
html[data-theme="dark"] .birthday-banner {
    background: linear-gradient(135deg, #3A1F2B, #3A2A1A);
    border-color: #6B3A50;
}

/* Baner wygasających punktów - analogiczny wzorzec do banera
   urodzinowego, widoczny WYŁĄCZNIE gdy faktycznie ma to znaczenie
   (okno ostrzeżenia, patrz Customer::daysUntilPointsExpiry()). */
.expiry-banner {
    background: linear-gradient(135deg, #FDEEE0, #FFF6EC);
    border: 1px solid #F0C69A;
    border-radius: var(--radius-md);
    padding: 1rem;
    display: flex;
    align-items: center;
    gap: 0.7rem;
    font-size: 0.85rem;
    margin-bottom: 1rem;
    color: var(--color-text);
}
.expiry-banner .icon { font-size: 1.6rem; flex-shrink: 0; }
html[data-theme="dark"] .expiry-banner {
    background: linear-gradient(135deg, #3A2A15, #2E2210);
    border-color: #6B4A1F;
}
.reward-item .name { font-weight: 600; font-size: 0.94rem; }
.reward-item .cost { font-size: 0.83rem; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.reward-item.unlocked .cost { color: var(--color-gold-text); font-weight: 600; }
.reward-item .name-row { display: flex; justify-content: space-between; align-items: center; }
.reward-mini-progress {
    margin-top: 0.4rem;
    height: 5px;
    background: var(--color-border);
    border-radius: 999px;
    overflow: hidden;
}
.reward-mini-progress .fill {
    height: 100%;
    background: var(--color-btn-bg, var(--color-primary));
    border-radius: 999px;
    transition: width 0.4s ease;
}

/* Etykieta grupy dnia ("Dziś"/"Wczoraj"/data) nad każdą grupą wpisów
   historii - patrz day_group_label() w helpers/functions.php. */
.history-day-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-text-muted);
    margin: 1.1rem 0 0.4rem;
}
.history-day-label:first-child { margin-top: 0; }

/* Pieczątka "Odebrano" przy zrealizowanej nagrodzie w historii -
   nawiązanie do papierowej karty lojalnościowej. */
.redeemed-stamp {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.05rem 0.4rem;
    border: 1.5px solid var(--color-success);
    border-radius: var(--radius-sm);
    color: var(--color-success);
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    transform: rotate(-4deg);
    vertical-align: middle;
}

/* Oś czasu historii - pionowa linia łącząca wpisy, przechodząca "za"
   ikonami dzięki box-shadow imitującemu przerwę w linii (patrz .tx-icon
   poniżej, nadpisane specyficznie w kontekście .history-timeline). */
.history-timeline { position: relative; }
.history-timeline::before {
    content: "";
    position: absolute;
    left: 14px;
    top: 14px;
    bottom: 14px;
    width: 2px;
    background: var(--color-border);
    z-index: 0;
}
.history-timeline .history-item { position: relative; z-index: 1; }
.history-timeline .tx-icon { position: relative; z-index: 1; box-shadow: 0 0 0 4px var(--color-bg-card); }
.history-timeline .reward-icon-duo { position: relative; z-index: 1; box-shadow: 0 0 0 4px var(--color-bg-card); }

.history-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.6rem;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--color-border);
}
.history-item .desc { font-weight: 500; font-size: 0.9rem; }
.history-item .date { font-size: 0.76rem; color: var(--color-text-muted); }
.history-item .points.positive { color: var(--color-success); font-weight: 600; font-variant-numeric: tabular-nums; }
.history-item .points.negative { color: var(--color-danger); font-weight: 600; font-variant-numeric: tabular-nums; }

/* Dolna nawigacja klienta */
.customer-bottom-nav {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: 0;
    width: 100%;
    max-width: 480px;
    /* Szkło mrożone - treść przewija się "pod spodem", widoczna niewyraźnie
       przez rozmycie, zamiast znikać pod twardą, nieprzezroczystą krawędzią
       (wzorzec z iOS). var(--color-bg-card) zamieniony na półprzezroczysty
       odpowiednik + backdrop-filter. Osobny wariant dla trybu ciemnego
       poniżej - inaczej biała przezroczystość prześwitywałaby na ciemnym tle. */
    background: rgba(255, 255, 255, 0.72);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-top: 1px solid rgba(226, 230, 236, 0.6);
    display: flex;
    padding: 0.5rem 0.5rem calc(0.5rem + env(safe-area-inset-bottom));
}
html[data-theme="dark"] .customer-bottom-nav {
    background: rgba(30, 34, 42, 0.72);
    border-top-color: rgba(44, 49, 59, 0.6);
}
.customer-bottom-nav a {
    position: relative;
    z-index: 1;
    flex: 1;
    text-align: center;
    padding: 0.5rem 0.2rem;
    border-radius: var(--radius-sm);
    color: var(--color-text-muted);
    font-size: 0.72rem;
    font-weight: 500;
}
/* Kropka nowości przy zakładce Nagrody - gdy właściciel doda nagrodę,
   której klient jeszcze nie widział (patrz Customer::hasUnseenRewards()).
   Pozycjonowana nad ikoną, w stylu powiadomień z większości aplikacji. */
.nav-dot {
    position: absolute;
    top: 2px;
    right: 22%;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--color-danger);
    border: 1.5px solid var(--color-bg-card);
}
.customer-bottom-nav a.active { color: var(--color-nav-pill-text, var(--color-primary)); font-weight: 600; }
/* Subtelna hierarchia wagi ikon - grubszy stroke na aktywnej zakładce niż
   na reszcie. Drugi, bardziej instynktowny sygnał "gdzie teraz jestem"
   obok koloru - działa nawet boczne widzenie. Nadpisuje atrybut
   stroke-width="2" wpisany wprost w znacznikach SVG (atrybuty
   prezentacyjne mają niższy priorytet w kaskadzie niż reguły CSS). */
.customer-bottom-nav svg { display: block; width: 20px; height: 20px; margin: 0 auto 0.15rem; stroke-width: 1.5; }
.customer-bottom-nav a.active svg { stroke-width: 2.25; }

.auth-screen { display: flex; flex-direction: column; justify-content: center; min-height: 100vh; padding: 1.5rem; }
.auth-screen .logo-block { text-align: center; margin-bottom: 2rem; }
/* Delikatny, jaśniejszy wariant odznaki logo TYLKO na ekranach logowania/
   rejestracji klienta - nadpisuje wspólny .brand-badge (wypełniony
   kolorem primary, dzielony z panelem właściciela) na subtelniejszy,
   duotone-podobny styl, spójny z resztą ikon w panelu klienta, bez
   dotykania wyglądu ekranów logowania właściciela. */
.auth-screen .brand-badge {
    background: var(--color-primary-light);
    color: var(--color-primary-dark);
}

/* ===== Ekrany logowania/rejestracji "v2" (runda 35) =====
   Wspólna struktura (plakietka, pola z ikonami, przycisk, dekoracje
   tła) PRZENIESIONA do app.css po audycie spójności wizualnej (runda
   48) - używana teraz przez OBA panele, nie tylko klienta. Tutaj
   zostaje WYŁĄCZNIE box korzyści poniżej, bo jego kolor zależy od
   --color-card-gradient-start (paleta karty klienta, bez fallbacku) -
   właściciel nie ma tej koncepcji, więc ma WŁASNY, analogiczny wariant
   w owner.css ze stałym kolorem marki zamiast zmiennej palety. */
.auth-v2-benefits {
    display: flex;
    gap: 0.9rem;
    align-items: flex-start;
    /* Jasny odcień WYBRANEJ palety (--color-card-gradient-start, już
       dostępna globalnie na <body>, patrz layouts/customer.php) -
       świadomie NIE reużywa pill_tint (istniejąca zmienna z CardPalette),
       bo ta jest zaprojektowana do INNEGO, znacznie bardziej stonowanego
       zastosowania (tło pastylki aktywnej zakładki) - dawała neutralnie
       szary, mało "kolorowy" box na ciemnych paletach, słabo oddając
       charakter marki widoczny w dostarczonym wzorcu. 30% mieszania -
       wartość zweryfikowana obliczeniowo (WCAG) jako maksymalna, przy
       której tekst opisu (#3D2233, stały - patrz niżej) wciąż ma
       bezpieczny kontrast na WSZYSTKICH 12 paletach. */
    background: color-mix(in srgb, var(--color-card-gradient-start) 30%, white);
    border-radius: 14px;
    padding: 1rem 1.1rem;
    margin-top: 1.8rem;
}
.auth-v2-benefits-icon {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: rgba(255,255,255,0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-card-gradient-start);
}
.auth-v2-benefits-icon svg { width: 20px; height: 20px; }
/* Tytuł: sam (nasycony) kolor palety dla CIEMNYCH palet - wyraźnie
   związany z marką. Dla JASNYCH palet ten sam kolor byłby ZA JASNY
   względem jaśniejszego tła (kontrast spada do ~1.2, katastrofa) -
   stąd wariant .on-light ze stałym, ciemnym tekstem (analogiczny
   mechanizm jak .loyalty-card.on-light, .auth-hero-card.on-light itd.
   w reszcie tego projektu). Zweryfikowane obliczeniowo (WCAG) dla
   wszystkich 12 palet PRZED napisaniem tego CSS. */
.auth-v2-benefits-title { font-weight: 700; font-size: 0.88rem; margin-bottom: 0.2rem; color: var(--color-card-gradient-start); }
.auth-v2-benefits.on-light .auth-v2-benefits-title { color: #3D2233; }
/* Opis: stały ciemny kolor niezależnie od palety - zweryfikowany jako
   bezpieczny (kontrast 7.7-13.3) na WSZYSTKICH 12 paletach przy tle
   30%, więc nie potrzebuje osobnego wariantu .on-light. */
.auth-v2-benefits-text { font-size: 0.8rem; color: #3D2233; line-height: 1.5; }
.auth-v2-benefits-text a { font-weight: 600; }

/* Panel konta klienta */
/* Karta profilowa na górze "Twoje konto" - punkt orientacyjny ekranu
   zamiast wchodzenia od razu w suchą listę pól. Ten sam gradientowy
   styl awatara co w panelu właściciela (runda 25), dla spójności. */
.profile-card {
    text-align: center;
    padding: 1.3rem 1rem;
    margin-bottom: var(--space-5);
}
.profile-avatar {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: linear-gradient(135deg, #4A6BA6, #22375E);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 800;
    font-size: 1.1rem;
    margin: 0 auto 0.6rem;
    box-shadow: 0 4px 10px rgba(34,55,94,0.3);
}
.profile-name { font-weight: 700; font-size: 0.98rem; }
.profile-since { font-size: 0.76rem; color: var(--color-text-muted); margin-top: 0.15rem; }

/* Karta "Poleć znajomym" na koncie klienta - własny kod polecający +
   udostępnianie. Ten sam gradientowy styl co karta lojalnościowa, żeby
   było jasne że to "coś od sklepu", nie zwykły element formularza. */
.referral-card {
    background: linear-gradient(135deg, var(--color-card-gradient-start, #22375E), var(--color-card-gradient-end, #33538F));
    border-radius: var(--radius-lg);
    padding: 1.1rem;
    color: #fff;
    text-align: center;
    margin-bottom: var(--space-5);
}
/* Wariant dla jasnych palet - ten sam mechanizm co .loyalty-card.on-light
   (karta główna) i .qr-pass-header.on-light (przepustka QR). Bez tego,
   biały tekst na jasnym (np. pudrowym) tle karty jest praktycznie
   nieczytelny - dokładnie ten błąd, który spowodował konieczność tej
   poprawki (kolor tła zaczął reagować na paletę, ale tekst pozostał
   sztywno biały). */
.referral-card.on-light { color: #3D2233; }
.referral-card.on-light .referral-code-box { background: rgba(61,34,51,0.1); }
.referral-card-label { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.8; }
.referral-code-box {
    background: rgba(255,255,255,0.15);
    border-radius: 10px;
    padding: 0.6rem;
    margin-top: 0.6rem;
    font-family: monospace;
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: 0.12em;
}
.referral-card-bonus { font-size: 0.78rem; opacity: 0.9; margin-top: 0.6rem; }
.referral-share-row { display: flex; gap: 0.5rem; margin-top: 0.8rem; }
.referral-share-row .btn { flex: 1; background: rgba(255,255,255,0.92); color: var(--color-accent-text, var(--color-primary-dark)); border: none; }
.referral-share-row .btn:hover { filter: brightness(0.95); }

.account-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.55rem 0;
    border-bottom: 1px solid var(--color-border);
}
.account-row:last-child { border-bottom: none; }
.account-label { font-size: 0.9rem; font-weight: 500; }
.account-value { font-size: 0.85rem; color: var(--color-text-muted); }

.consent-toggle {
    width: 42px;
    height: 24px;
    border-radius: 999px;
    background: var(--color-border);
    border: none;
    position: relative;
    flex-shrink: 0;
    cursor: pointer;
    padding: 0;
}
.consent-toggle::after {
    content: "";
    position: absolute;
    top: 3px; left: 3px;
    width: 18px; height: 18px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0,0,0,0.2);
    transition: left 0.15s ease;
}
.consent-toggle.on { background: var(--color-btn-bg, var(--color-primary)); }
.consent-toggle.on::after { left: 21px; }

/* Zakładki logowania: E-mail (domyślna/priorytetowa) i Kod SMS (zapasowa). */
.login-tabs {
    display: flex;
    gap: 0.3rem;
    background: var(--color-bg);
    border-radius: var(--radius-sm);
    padding: 0.25rem;
    margin-bottom: var(--space-5);
}
.login-tab {
    flex: 1;
    padding: 0.5rem;
    border: none;
    background: transparent;
    border-radius: var(--radius-sm);
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--color-text-muted);
    cursor: pointer;
}
.login-tab.active { background: #fff; color: var(--color-accent-text, var(--color-primary)); font-weight: 600; box-shadow: var(--shadow-soft); }
.link-button {
    background: none;
    border: none;
    padding: 0;
    color: var(--color-accent-text, var(--color-primary));
    font-weight: 600;
    font-size: inherit;
    cursor: pointer;
    text-decoration: underline;
}

/* Karta z zachętą do dokończenia konta (dodania e-maila/hasła) na ekranie głównym klienta. */
.account-prompt {
    background: var(--color-primary-light);
    border-radius: var(--radius-md);
    padding: 1rem;
    margin-bottom: var(--space-5);
}
.account-prompt .title { font-weight: 600; font-size: 0.9rem; color: var(--color-primary-dark); margin-bottom: 0.3rem; }
.account-prompt .sub { font-size: 0.82rem; color: var(--color-text-muted); margin-bottom: 0.7rem; }

/* Zaprojektowane, dwukolorowe ilustracje pustych stanów (zamiast cienkiej
   ikony liniowej) - w tych rzadkich, ale zauważalnych momentach (brak
   historii, brak nagród) nadają charakter, nie wyglądają jak zapomniany
   placeholder. Nadpisuje domyślne wymiary 32x32 z app.css - te SVG mają
   własne, większe wymiary i kolory wypełnienia, nie stroke=currentColor. */
.empty-state-illustrated svg {
    width: 72px;
    height: 72px;
    stroke: none;
    margin-bottom: 0.8rem;
}
