Files
Luncher/client/src/FlyingButterflies.scss
T
Stánek PavelandClaude Opus 4.8 377a350211
CI / Generate TypeScript types (push) Successful in 15s
CI / Server unit tests (push) Successful in 24s
CI / Build server (push) Successful in 31s
CI / Build client (push) Successful in 1m0s
CI / Playwright E2E tests (push) Successful in 1m30s
CI / Build and push Docker image (push) Successful in 1m6s
CI / Notify (push) Successful in 5s
feat: anti-bot inspekce a vězení + přepínač hraní/objednávání
- přepínač Hraní/Objednávání; v objednávání hra nic neruší, v hraní herní vrstva odstíní kliknutí
- scéna žije trvale (přepnutí režimu neobejde pavučinu, omráčení, zabavenou síťku ani ban)
- anti-bot: ovládání jen reálnou myší (isTrusted/webdriver), občasná motýlí inspekce (chyť korunovaného)
- neúspěch inspekce = zabavená síťka 45 s (opticky skrytá); opakovaně = serverový ban s mříží a odpočtem
- během banu server nekredituje úlovky (F5/přepnutí-proof)
- větší síťka se teď viditelně zvětší hned po koupi
- changelog route přeskočí vadný JSON (nespadne celý endpoint) + oprava changelogu

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 12:56:20 +02:00

750 lines
17 KiB
SCSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.flying-butterflies {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 2;
overflow: hidden;
// V režimu hraní herní vrstva zachytává kliknutí, aby se nedalo omylem
// překliknout do objednávek. V režimu objednávání propouští vše dál (none).
&.playing {
pointer-events: auto;
}
}
// Přepínač režimu hraní / objednávání
.butterfly-mode-toggle {
position: fixed;
bottom: 16px;
right: 16px;
z-index: 7;
pointer-events: auto;
padding: 8px 14px;
border: none;
border-radius: 999px;
background: rgba(33, 37, 41, 0.88);
color: #fff;
font-weight: 700;
font-size: 0.9rem;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(4px);
transition: background 0.15s ease, transform 0.08s ease;
&:hover { background: rgba(33, 37, 41, 1); }
&:active { transform: scale(0.96); }
&.playing {
background: #2f9e44;
&:hover { background: #2b8a3e; }
}
}
// Vrstva herních prvků (síťka, škůdci, pavučina) skrývá se v režimu objednávání
.butterfly-game-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.butterfly-scene {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
// Vnější element pozici a natočení nastavuje JS přes transform
> div {
position: absolute;
top: 0;
left: 0;
width: 34px;
height: 34px;
will-change: transform;
}
}
// Vnitřní element se sprite motýla máchá křídly (scaleX kolem svislé osy těla)
.butterfly-sprite {
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
animation: butterfly-flap 0.45s ease-in-out infinite;
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.15));
// Vzácný zlatý motýl WOW: silná pulzující zlatá záře + duhový nádech
&.golden {
animation:
butterfly-flap 0.35s ease-in-out infinite,
butterfly-golden-glow 1.4s ease-in-out infinite;
}
}
@keyframes butterfly-golden-glow {
0%,
100% {
filter:
saturate(1.15) hue-rotate(0deg)
drop-shadow(0 0 5px rgba(255, 215, 80, 0.85))
drop-shadow(0 0 10px rgba(255, 180, 40, 0.55))
drop-shadow(0 1px 1px rgba(0, 0, 0, 0.2));
}
50% {
filter:
saturate(1.4) hue-rotate(-16deg)
drop-shadow(0 0 13px rgba(255, 235, 130, 1))
drop-shadow(0 0 24px rgba(255, 190, 50, 0.95))
drop-shadow(0 1px 1px rgba(0, 0, 0, 0.2));
}
}
@keyframes butterfly-flap {
0%,
100% {
transform: scaleX(1);
}
50% {
transform: scaleX(0.35);
}
}
// Korunovaný motýl (anti-bot inspekce) nepřehlédnutelná duhová záře
.butterfly-sprite.royal {
animation: butterfly-flap 0.35s ease-in-out infinite, butterfly-royal-glow 0.9s ease-in-out infinite;
}
@keyframes butterfly-royal-glow {
0%, 100% {
filter: drop-shadow(0 0 8px rgba(255, 215, 80, 1)) drop-shadow(0 0 16px rgba(120, 200, 255, 0.8));
}
50% {
filter: drop-shadow(0 0 16px rgba(255, 120, 220, 1)) drop-shadow(0 0 28px rgba(120, 255, 160, 0.9));
}
}
// Černá můra zlověstný tmavý nádech
.butterfly-sprite.moth {
filter: drop-shadow(0 0 4px rgba(120, 60, 160, 0.6)) drop-shadow(0 1px 1px rgba(0, 0, 0, 0.4));
animation: butterfly-flap 0.5s ease-in-out infinite, butterfly-moth-pulse 1.6s ease-in-out infinite;
}
@keyframes butterfly-moth-pulse {
0%, 100% { filter: drop-shadow(0 0 4px rgba(120, 60, 160, 0.5)) drop-shadow(0 1px 1px rgba(0, 0, 0, 0.4)); }
50% { filter: drop-shadow(0 0 9px rgba(150, 40, 40, 0.8)) drop-shadow(0 1px 1px rgba(0, 0, 0, 0.4)); }
}
// --- Havěť (ptáci a vosy) ----------------------------------------------------
.butterfly-critter {
position: absolute;
top: 0;
left: 0;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
pointer-events: none;
will-change: transform;
filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.2));
z-index: 3;
// Vosa jde zaklikat (plácačka)
&.wasp {
pointer-events: auto;
cursor: crosshair;
touch-action: none;
}
// Netopýr jde zahnat klikáním
&.bat {
pointer-events: auto;
cursor: crosshair;
touch-action: none;
z-index: 5;
filter: drop-shadow(0 0 6px rgba(60, 40, 90, 0.8));
}
&.hit {
animation: butterfly-stalker-hit 0.18s ease-out;
}
}
// Pavučina přes síťku blokuje chytání, dokud ji hráč nestrhne
.butterfly-web {
position: fixed;
top: 0;
left: 0;
width: 120px;
height: 120px;
background-image: url(/spider.svg);
background-repeat: no-repeat;
background-position: center;
background-size: contain;
pointer-events: auto;
cursor: crosshair;
touch-action: none;
z-index: 6;
opacity: 0.9;
filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.4));
&.hit {
animation: butterfly-stalker-hit 0.15s ease-out;
}
}
// --- Síťka na chytání --------------------------------------------------------
.butterfly-net {
position: fixed;
top: 0;
left: 0;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
pointer-events: auto;
cursor: grab;
z-index: 4;
will-change: transform;
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.25));
transition: filter 0.15s ease;
touch-action: none;
user-select: none;
&.grabbed {
cursor: grabbing;
filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.35));
}
// Prémiová (zlatá) síťka zlatá záře a jemný pulz
&.premium {
filter: drop-shadow(0 0 8px rgba(255, 200, 60, 0.85)) drop-shadow(0 2px 3px rgba(0, 0, 0, 0.25));
animation: butterfly-net-glow 1.3s ease-in-out infinite;
}
// Protržená síťka nedá se chytat
&.torn {
cursor: not-allowed;
opacity: 0.9;
filter: drop-shadow(0 2px 3px rgba(150, 30, 30, 0.35));
}
&.catch-pop {
animation: butterfly-net-pop 0.3s ease-out;
}
&.stung {
animation: butterfly-net-stung 0.4s ease-in-out;
}
// Záblesk při chycení černé můry (chyba)
&.moth-hit {
animation: butterfly-net-moth 0.4s ease-out;
}
// Omráčená síťka (po žihnutí vosou) po celou dobu, kdy nejde chytat.
// Má přednost před grabbed kurzorem.
&.stunned {
cursor: not-allowed !important;
animation: butterfly-net-dizzy 0.9s ease-in-out infinite;
}
}
// Zešednutí + červený nádech, ať je jasné, že síťka teď nechytá.
// NEanimujeme transform (ten řídí JS inline) jen filter.
@keyframes butterfly-net-dizzy {
0%,
100% {
filter: grayscale(0.7) brightness(0.85) drop-shadow(0 0 5px rgba(217, 72, 15, 0.55));
}
50% {
filter: grayscale(0.85) brightness(0.8) drop-shadow(0 0 9px rgba(217, 72, 15, 0.8));
}
}
@keyframes butterfly-net-pop {
0% {
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.25)) brightness(1);
}
40% {
filter: drop-shadow(0 2px 8px rgba(120, 200, 120, 0.7)) brightness(1.25);
}
100% {
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.25)) brightness(1);
}
}
@keyframes butterfly-net-glow {
0%,
100% {
filter: drop-shadow(0 0 6px rgba(255, 200, 60, 0.6)) drop-shadow(0 2px 3px rgba(0, 0, 0, 0.25));
}
50% {
filter: drop-shadow(0 0 12px rgba(255, 220, 100, 1)) drop-shadow(0 2px 3px rgba(0, 0, 0, 0.25));
}
}
// Pozn.: NEanimujeme transform ten nastavuje JS inline (poloha síťky).
// Žihnutí vosou proto naznačíme jen červeným zábleskem filtru.
@keyframes butterfly-net-stung {
0%,
100% {
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.25)) brightness(1);
}
30% {
filter: drop-shadow(0 0 8px rgba(217, 72, 15, 0.85)) brightness(1.1) hue-rotate(-15deg);
}
}
// Prchavé texty (chycení, protržení, žihnutí)
.butterfly-catch-fx,
.butterfly-tear-fx,
.butterfly-sting-fx {
position: fixed;
margin: -12px 0 0 10px;
font-weight: 700;
font-size: 1.1rem;
pointer-events: none;
z-index: 5;
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.8);
animation: butterfly-catch-float 0.8s ease-out forwards;
}
.butterfly-catch-fx {
color: #2f9e44;
&.golden {
color: #e0a200;
font-size: 1.35rem;
text-shadow: 0 0 6px rgba(255, 220, 100, 0.9), 0 1px 2px rgba(0, 0, 0, 0.3);
}
&.moth {
color: #c92a2a;
font-size: 1.35rem;
text-shadow: 0 0 6px rgba(120, 40, 160, 0.7), 0 1px 2px rgba(0, 0, 0, 0.3);
}
}
@keyframes butterfly-net-moth {
0%, 100% { filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.25)) brightness(1); }
30% { filter: drop-shadow(0 0 9px rgba(150, 40, 160, 0.9)) brightness(0.8); }
}
.butterfly-tear-fx {
color: #c92a2a;
}
.butterfly-sting-fx {
color: #d9480f;
}
@keyframes butterfly-catch-float {
0% {
opacity: 0;
transform: translateY(0) scale(0.9);
}
20% {
opacity: 1;
transform: translateY(-6px) scale(1);
}
100% {
opacity: 0;
transform: translateY(-40px) scale(1);
}
}
// --- HUD: počítadlo, mince, úroveň, kombo, hlášky ----------------------------
.butterfly-hud {
position: fixed;
bottom: 16px;
left: 16px;
z-index: 5;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
// HUD sám o sobě neblokuje klikání; interaktivní je jen tlačítko opravy
pointer-events: none;
}
.butterfly-shop-open {
pointer-events: auto;
align-self: flex-start;
padding: 6px 12px;
border: none;
border-radius: 999px;
background: #f59e1e;
color: #fff;
font-weight: 700;
font-size: 0.85rem;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
transition: background 0.15s ease, transform 0.08s ease;
&:hover { background: #e8890a; }
&:active { transform: scale(0.96); }
}
.butterfly-counter {
display: inline-flex;
flex-direction: column;
gap: 4px;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.88);
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 14px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(4px);
font-weight: 600;
font-size: 0.95rem;
color: #333;
user-select: none;
pointer-events: auto;
cursor: pointer;
transition: box-shadow 0.15s ease;
&:hover {
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.22);
}
&.bump {
animation: butterfly-counter-bump 0.3s ease-out;
}
}
.butterfly-counter-row {
display: inline-flex;
align-items: center;
gap: 6px;
}
.butterfly-counter-icon,
.butterfly-coin-icon {
display: inline-block;
width: 20px;
height: 20px;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
.butterfly-counter-icon {
background-image: url(/butterfly-orange.svg);
}
.butterfly-coin-icon {
background-image: url(/coin.svg);
margin-left: 4px;
}
.butterfly-counter-value {
min-width: 1ch;
text-align: center;
font-variant-numeric: tabular-nums;
}
.butterfly-level {
display: flex;
flex-direction: column;
gap: 3px;
}
.butterfly-level-title {
font-size: 0.72rem;
font-weight: 600;
color: #6b5b2e;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
.butterfly-progress {
width: 100%;
height: 5px;
background: rgba(0, 0, 0, 0.1);
border-radius: 999px;
overflow: hidden;
}
.butterfly-progress-bar {
height: 100%;
background: linear-gradient(90deg, #ffcb3d, #e0a200);
border-radius: 999px;
transition: width 0.4s ease;
}
.butterfly-pests {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.8rem;
font-weight: 700;
}
.butterfly-pest {
color: #868e96;
font-variant-numeric: tabular-nums;
&.warn {
color: #d9480f;
}
&.ok {
color: #2f9e44;
}
}
.butterfly-torn-note {
margin-top: 4px;
font-size: 0.76rem;
font-weight: 600;
color: #c92a2a;
}
.butterfly-action-btn {
margin-top: 4px;
padding: 5px 10px;
border: none;
border-radius: 999px;
background: #1c7ed6;
color: #fff;
font-weight: 700;
font-size: 0.82rem;
cursor: pointer;
pointer-events: auto;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
transition: background 0.15s ease, transform 0.1s ease;
&:hover:not(:disabled) {
background: #1971c2;
}
&:active:not(:disabled) {
transform: scale(0.96);
}
&:disabled {
background: #adb5bd;
cursor: not-allowed;
}
&.danger {
background: #c92a2a;
&:hover:not(:disabled) {
background: #e03131;
}
}
}
// --- Bossové (zloděj / housenka) ---------------------------------------------
.butterfly-stalker {
position: fixed;
top: 0;
left: 0;
width: 56px;
display: flex;
flex-direction: column;
align-items: center;
pointer-events: auto;
cursor: crosshair;
user-select: none;
touch-action: none;
z-index: 6;
will-change: transform;
&.hit .stalker-emoji {
animation: butterfly-stalker-hit 0.18s ease-out;
}
}
.stalker-emoji {
font-size: 44px;
line-height: 1;
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.35));
}
.stalker-hp {
width: 46px;
height: 6px;
margin-bottom: 3px;
background: rgba(0, 0, 0, 0.25);
border-radius: 999px;
overflow: hidden;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.stalker-hp-inner {
height: 100%;
width: 100%;
background: linear-gradient(90deg, #ff6b6b, #c92a2a);
border-radius: 999px;
transition: width 0.1s linear;
}
@keyframes butterfly-stalker-hit {
0% { transform: scale(1) rotate(0); }
50% { transform: scale(0.82) rotate(-8deg); filter: brightness(1.6); }
100% { transform: scale(1) rotate(0); }
}
// Efekt při plácnutí vosy
.butterfly-swat-fx {
position: fixed;
margin: -14px 0 0 -6px;
font-size: 1.4rem;
pointer-events: none;
z-index: 5;
animation: butterfly-catch-float 0.8s ease-out forwards;
}
.butterfly-combo {
padding: 4px 10px;
border-radius: 999px;
background: rgba(224, 162, 0, 0.92);
color: #fff;
font-weight: 800;
font-size: 0.9rem;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
animation: butterfly-counter-bump 0.3s ease-out;
}
.butterfly-flash {
max-width: 260px;
padding: 8px 12px;
border-radius: 12px;
background: rgba(33, 37, 41, 0.92);
color: #fff;
font-weight: 600;
font-size: 0.85rem;
line-height: 1.25;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.25);
animation: butterfly-flash-in 0.25s ease-out;
pointer-events: auto;
cursor: pointer;
}
@keyframes butterfly-flash-in {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes butterfly-counter-bump {
0% {
transform: scale(1);
}
40% {
transform: scale(1.18);
}
100% {
transform: scale(1);
}
}
// Ohleduplnost k uživatelům, kteří nechtějí pohyb
@media (prefers-reduced-motion: reduce) {
.butterfly-sprite,
.butterfly-sprite.golden {
animation: none;
}
.butterfly-net.catch-pop,
.butterfly-net.premium,
.butterfly-net.stung,
.butterfly-net.stunned,
.butterfly-counter.bump,
.butterfly-combo {
animation: none;
}
// Bez animace aspoň staticky ukážeme, že síťka je omráčená
.butterfly-net.stunned {
filter: grayscale(0.8) brightness(0.82) drop-shadow(0 0 6px rgba(217, 72, 15, 0.7));
}
// Zlatý motýl bez animace aspoň staticky září
.butterfly-sprite.golden {
filter: saturate(1.3) drop-shadow(0 0 8px rgba(255, 215, 80, 0.95)) drop-shadow(0 0 16px rgba(255, 190, 50, 0.7));
}
}
// --- Anti-bot inspekce a vězení ---------------------------------------------
.butterfly-inspection {
position: fixed;
top: 16px;
left: 50%;
transform: translateX(-50%);
z-index: 8;
pointer-events: none;
max-width: 92vw;
padding: 10px 16px;
border-radius: 999px;
background: rgba(28, 126, 214, 0.95);
color: #fff;
font-weight: 700;
font-size: 0.95rem;
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.3);
animation: butterfly-flash-in 0.25s ease-out;
}
.butterfly-jail {
position: fixed;
inset: 0;
z-index: 6;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
}
// Svislé mříže přes celou obrazovku
.butterfly-jail-bars {
position: absolute;
inset: 0;
background:
repeating-linear-gradient(
90deg,
rgba(20, 22, 28, 0.92) 0px,
rgba(20, 22, 28, 0.92) 14px,
rgba(20, 22, 28, 0) 14px,
rgba(20, 22, 28, 0) 70px
);
box-shadow: inset 0 0 120px rgba(0, 0, 0, 0.6);
}
.butterfly-jail-note {
position: relative;
text-align: center;
padding: 22px 26px;
border-radius: 16px;
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
max-width: 90vw;
.jail-emoji { font-size: 2.4rem; }
.jail-title { font-weight: 800; font-size: 1.2rem; color: #c92a2a; margin-top: 4px; }
.jail-sub { margin-top: 6px; font-size: 0.95rem; color: #333; font-variant-numeric: tabular-nums; }
.jail-hint { margin-top: 8px; font-size: 0.8rem; color: #868e96; }
}
@media (prefers-reduced-motion: reduce) {
.butterfly-sprite.royal { animation: none; }
}