feat: nový vzhled Gudetama
CI / Generate TypeScript types (push) Successful in 11s
CI / Server unit tests (push) Successful in 26s
CI / Build server (push) Successful in 28s
CI / Build client (push) Successful in 42s
CI / Playwright E2E tests (push) Successful in 2m5s
CI / Build and push Docker image (push) Canceled after 42s
CI / Notify (push) Canceled after 0s
CI / Generate TypeScript types (push) Successful in 11s
CI / Server unit tests (push) Successful in 26s
CI / Build server (push) Successful in 28s
CI / Build client (push) Successful in 42s
CI / Playwright E2E tests (push) Successful in 2m5s
CI / Build and push Docker image (push) Canceled after 42s
CI / Notify (push) Canceled after 0s
This commit is contained in:
10 files changed
+917
-5
No files matched your search
@@ -23,6 +23,7 @@ import './themes/frieren.scss';
|
||||
import './themes/mlpg5.scss';
|
||||
import './themes/aggretsuko.scss';
|
||||
import './themes/sumikko.scss';
|
||||
import './themes/gudetama.scss';
|
||||
import { faCircleCheck, faNoteSticky, faTrashCan, faComment } from '@fortawesome/free-regular-svg-icons';
|
||||
import { useSettings } from './context/settings';
|
||||
import Footer from './components/Footer';
|
||||
|
||||
@@ -164,6 +164,17 @@
|
||||
outline-offset: -5px;
|
||||
}
|
||||
|
||||
// Styl Gudetama: emoji na bílku se žloutkovým lemem
|
||||
.eater-badge-gudetama {
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
border-color: #fffdf5;
|
||||
background: radial-gradient(circle at 40% 35%, #fffdf5, #fff3c4);
|
||||
box-shadow: 0 0 0 2px #f5b301;
|
||||
}
|
||||
|
||||
// Odznak a jméno drží na jednom řádku (úzký sloupec „Člen" na stránce objednávek)
|
||||
.user-info:has(.eater-badge) {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -85,6 +85,12 @@ const SK_LEVELS = [
|
||||
{ minItems: 2, emoji: '🥒', title: 'Úroveň Penguin? — k obědu ještě okurka, aby se cítil jako kappa' },
|
||||
];
|
||||
|
||||
/** Úrovně ve stylu Gudetama — emoji na bílku se žloutkovým lemem. Seřazeno od nejvyšší úrovně. */
|
||||
const GT_LEVELS = [
|
||||
{ minItems: 3, emoji: '🍳', title: 'Úroveň Nisetama-san — sní to všechno sám, Gudetamovi se jíst nechce' },
|
||||
{ minItems: 2, emoji: '🥓', title: 'Úroveň slaniny — k obědu ještě plátek slaniny… ale jen jako deka' },
|
||||
];
|
||||
|
||||
/** Úrovně podle stylu pro odznaky s emoji. */
|
||||
const EMOJI_LEVELS: Partial<Record<StyleTheme, typeof MLP_LEVELS>> = {
|
||||
mlp: MLP_LEVELS,
|
||||
@@ -99,6 +105,7 @@ const EMOJI_LEVELS: Partial<Record<StyleTheme, typeof MLP_LEVELS>> = {
|
||||
mlpg5: G5_LEVELS,
|
||||
aggretsuko: AGG_LEVELS,
|
||||
sumikko: SK_LEVELS,
|
||||
gudetama: GT_LEVELS,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
@@ -106,7 +113,7 @@ type Props = {
|
||||
items: number,
|
||||
};
|
||||
|
||||
/** Odznak s postavou u strávníků s více jídly — jen ve stylech Umamusume, My Little Pony, Hell Girl, K-ON!, Bocchi the Rock!, Evangelion, Yuru Camp△, Spy × Family, Apothecary Diaries, Frieren, My Little Pony G5, Aggretsuko a Sumikko Gurashi. */
|
||||
/** Odznak s postavou u strávníků s více jídly — jen ve stylech Umamusume, My Little Pony, Hell Girl, K-ON!, Bocchi the Rock!, Evangelion, Yuru Camp△, Spy × Family, Apothecary Diaries, Frieren, My Little Pony G5, Aggretsuko, Sumikko Gurashi a Gudetama. */
|
||||
export default function EaterBadge({ items }: Readonly<Props>) {
|
||||
const styleTheme = useSettings()?.styleTheme;
|
||||
const isUma = styleTheme === 'umamusume';
|
||||
|
||||
@@ -1116,3 +1116,78 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Konfety ve stylu Gudetama
|
||||
// ============================================
|
||||
.confetti-gt {
|
||||
filter: drop-shadow(0 3px 2px rgb(74 53 32 / 0.25));
|
||||
}
|
||||
|
||||
// Štítky jako líně rozteklé bubliny
|
||||
.confetti-gt-badge {
|
||||
padding: 4px 16px;
|
||||
border-radius: 999px 999px 999px 999px / 60% 60% 40% 40%;
|
||||
border: 2px solid #fffdf5;
|
||||
color: #fffdf5;
|
||||
font-family: 'Fredoka', 'Nunito', sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: 1.15rem;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 1px 0 rgb(74 53 32 / 0.3);
|
||||
}
|
||||
|
||||
// Nápis „ぐでたま…" — bílek se žloutkem, dopadne na pánev a líně se rozteče
|
||||
.confetti-gt-banner {
|
||||
position: absolute;
|
||||
top: 38%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 14px 48px 16px;
|
||||
font-family: 'Fredoka', 'Nunito', sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: clamp(1.8rem, 7vw, 4rem);
|
||||
white-space: nowrap;
|
||||
color: #4a3520;
|
||||
background:
|
||||
radial-gradient(circle at 50% 50%, rgb(255 210 63 / 0.35) 0 30%, transparent 60%),
|
||||
#fffdf5;
|
||||
border-radius: 58% 42% 55% 45% / 60% 55% 45% 40%;
|
||||
box-shadow: 0 6px 0 #efe3c4, 0 18px 34px rgb(0 0 0 / 0.25);
|
||||
transform-origin: 50% 100%;
|
||||
animation: gt-banner 2.8s ease both;
|
||||
}
|
||||
|
||||
@keyframes gt-banner {
|
||||
0% { transform: translate(-50%, -160%) scale(0.8); opacity: 0; }
|
||||
14% { transform: translate(-50%, -50%) scale(1); opacity: 1; animation-timing-function: ease-out; }
|
||||
20% { transform: translate(-50%, -50%) scale(1.12, 0.82); }
|
||||
30% { transform: translate(-50%, -50%) scale(1, 1); }
|
||||
// Gude~ — pomalé rozteklé usazení
|
||||
55%, 82% { transform: translate(-50%, -48%) scale(1.06, 0.92); opacity: 1; }
|
||||
100% { transform: translate(-50%, -40%) scale(1.25, 0.6); opacity: 0; }
|
||||
}
|
||||
|
||||
// Postava s hláškou — žloutek na bílku a bublina jako bílek
|
||||
.confetti-gudetama-voice {
|
||||
.confetti-mlp-avatar {
|
||||
border: 4px solid #fffdf5;
|
||||
background: radial-gradient(circle at 34% 30%, #fff6c8 0 6px, #ffd23f 10px, #f5b301 100%);
|
||||
box-shadow: 0 0 0 3px var(--mlp-speaker, #f5b301), 0 6px 12px rgb(0 0 0 / 0.25);
|
||||
}
|
||||
|
||||
.confetti-mlp-bubble {
|
||||
background: #fffdf5;
|
||||
color: #4a3520;
|
||||
border: 2px solid var(--mlp-speaker, #f5b301);
|
||||
border-radius: 58% 42% 55% 45% / 40% 45% 55% 60%;
|
||||
padding: 14px 22px;
|
||||
box-shadow: 0 4px 0 #efe3c4;
|
||||
font-family: 'Fredoka', 'Nunito', sans-serif;
|
||||
|
||||
p {
|
||||
font-weight: 500;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +196,21 @@ const SK_SPEAKERS = [
|
||||
{ name: 'Zassou', emoji: '🌱', color: '#9cc06f', quote: 'Jednou ze mě bude krásná kytice. Do té doby si dám oběd.' },
|
||||
{ name: 'Tapioka', emoji: '🧋', color: '#e8a0b8', quote: 'Nás perliček zbylo na dně hodně… tak jdeme na to!' },
|
||||
];
|
||||
const GT_EMOJI = ['🍳', '🥚', '🥓', '🍞', '🧂', '🥢', '🍚', '😪', '💤', '🍜', '🧈', '🍣', '🫠', '🥄'];
|
||||
// Hlášky Gudetamy (gude~, meh…, nechce se mi, slanina jako deka...)
|
||||
const GT_BADGES = ['ぐでたま', 'Gude~', 'Meh…', 'Nechce se mi…', 'Ještě 5 minut', 'Slanina = deka', 'Na měkko', 'Volské oko', 'Tamagoyaki', 'Zítra…'];
|
||||
const GT_BADGE_COLORS = ['#f5b301', '#e08a00', '#c8553d', '#6b4a2e', '#d9a066', '#8a7a64'];
|
||||
/** Gudetama ve všech podobách a jeho okolí s „hláškou" po finálním výsledku. */
|
||||
const GT_SPEAKERS = [
|
||||
{ name: 'Gudetama', emoji: '🍳', color: '#f5b301', quote: 'Oběd…? Meh… přineste mi ho sem… nechce se mi…' },
|
||||
{ name: 'Nisetama-san', emoji: '🥚', color: '#c9a227', quote: 'Gudetamo, vstávej! Jde se na oběd! …No tak, aspoň se převal!' },
|
||||
{ name: 'Shakipiyo', emoji: '🐣', color: '#f2a03a', quote: 'Píp! Já tam doběhnu první! Gudetamo, dělej!' },
|
||||
{ name: 'Gudetama na měkko', emoji: '🥚', color: '#e8c06a', quote: 'Jsem na měkko… a takhle i zůstanu. Gude~' },
|
||||
{ name: 'Tamagoyaki Gudetama', emoji: '🍱', color: '#e0a830', quote: 'Jsem srolovaný do omelety… jako do peřiny. Dobrou.' },
|
||||
{ name: 'Slanina', emoji: '🥓', color: '#c8553d', quote: '(přikryje Gudetamu jako deka a usne s ním)' },
|
||||
{ name: 'Gudetama v ramenu', emoji: '🍜', color: '#d9a066', quote: 'Plavu si tu v ramenu… vývar je teplý, nechce se mi ven.' },
|
||||
{ name: 'Gudetama na rýži', emoji: '🍚', color: '#a3927c', quote: 'Na rýži se leží nejlíp… itadakimasu… zítra.' },
|
||||
];
|
||||
/** Styly, jejichž konfety tvoří emoji a samolepky s hláškami (a po finále promluví postava s emoji). */
|
||||
const EMOJI_CONFETTI: Partial<Record<WheelTheme, {
|
||||
prefix: string, badges: string[], colors: string[], emoji: string[],
|
||||
@@ -214,6 +229,7 @@ const EMOJI_CONFETTI: Partial<Record<WheelTheme, {
|
||||
mlpg5: { prefix: 'confetti-g5', badges: G5_BADGES, colors: G5_BADGE_COLORS, emoji: G5_EMOJI, speakers: G5_SPEAKERS, banner: '☀️ Make Your Mark! ☀️' },
|
||||
aggretsuko: { prefix: 'confetti-agg', badges: AGG_BADGES, colors: AGG_BADGE_COLORS, emoji: AGG_EMOJI, speakers: AGG_SPEAKERS, banner: '🤘 RAAAAAH!!! 🎤' },
|
||||
sumikko: { prefix: 'confetti-sk', badges: SK_BADGES, colors: SK_BADGE_COLORS, emoji: SK_EMOJI, speakers: SK_SPEAKERS, banner: '🍤 すみっコぐらし 🐧' },
|
||||
gudetama: { prefix: 'confetti-gt', badges: GT_BADGES, colors: GT_BADGE_COLORS, emoji: GT_EMOJI, speakers: GT_SPEAKERS, banner: '🍳 ぐでたま… 🥓' },
|
||||
};
|
||||
const DURATION_MS = 6400;
|
||||
|
||||
@@ -367,7 +383,7 @@ export default function WheelConfetti({ theme, burstKey, big = false }: Readonly
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [burstKey]);
|
||||
|
||||
// Ostatní tematické styly: hlášku pronese náhodná postava (Mane 6, Ai Enma, Houkago Tea Time, Kessoku Band, piloti NERV, Nokuru, Forgerovi, vnitřní palác, družina Frieren, poníci z Maretime Bay, kancelář Retsuko, postavičky z koutků...)
|
||||
// Ostatní tematické styly: hlášku pronese náhodná postava (Mane 6, Ai Enma, Houkago Tea Time, Kessoku Band, piloti NERV, Nokuru, Forgerovi, vnitřní palác, družina Frieren, poníci z Maretime Bay, kancelář Retsuko, postavičky z koutků, Gudetama...)
|
||||
const emojiConfetti = EMOJI_CONFETTI[theme];
|
||||
const emojiSpeaker = useMemo(
|
||||
() => big && emojiConfetti ? pick(emojiConfetti.speakers) : undefined,
|
||||
|
||||
@@ -3654,6 +3654,266 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// STYL GUDETAMA kola štěstí (dle stylu zvoleného v nastavení).
|
||||
// Kolo leží na litinové pánvi: výseče v barvách snídaně (žloutek, bílek,
|
||||
// slanina, toust, sójová omáčka, nori), střed jako žloutek.
|
||||
// ============================================
|
||||
.spin-wheel-modal.spin-theme-gudetama {
|
||||
--gt-font: 'Nunito', sans-serif;
|
||||
--gt-display: 'Fredoka', 'Nunito', sans-serif;
|
||||
--gt-yolk: #ffd23f;
|
||||
--gt-yolk-shadow: #d99a00;
|
||||
--gt-ink: #4a3520;
|
||||
--gt-line: #efe3c4;
|
||||
--gt-panel: #ffffff;
|
||||
--wheel-c0: #ffd23f;
|
||||
--wheel-c1: #f5e6b8;
|
||||
--wheel-c2: #f2a7a0;
|
||||
--wheel-c3: #e8c08a;
|
||||
--wheel-c4: #c9a07a;
|
||||
--wheel-c5: #b8d48f;
|
||||
--wheel-label: #4a3520;
|
||||
--wheel-rim: #2b2725;
|
||||
--wheel-hub: #ffd23f;
|
||||
|
||||
font-family: var(--gt-font);
|
||||
|
||||
.modal-content {
|
||||
border: 2px solid var(--gt-line);
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(rgb(74 53 32 / 0.05) 1.5px, transparent 1.5px) 0 0 / 46px 46px,
|
||||
linear-gradient(90deg, rgb(74 53 32 / 0.05) 1.5px, transparent 1.5px) 0 0 / 46px 46px,
|
||||
#fbf8f1;
|
||||
color: var(--gt-ink);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background:
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='10'%3E%3Cpath d='M0 3Q12 0 24 3T48 3V7Q36 10 24 7T0 7Z' fill='%23f2a7a0'/%3E%3Cpath d='M0 5Q12 2 24 5T48 5' stroke='%23c8553d' stroke-width='1.5' fill='none'/%3E%3C/svg%3E") left bottom / 48px 10px repeat-x,
|
||||
linear-gradient(180deg, #ffdc5c, var(--gt-yolk));
|
||||
border-bottom: none;
|
||||
padding-bottom: calc(var(--bs-modal-header-padding-y, 1rem) + 8px);
|
||||
|
||||
h2 {
|
||||
color: var(--gt-ink);
|
||||
font-family: var(--gt-display);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border-top: 2px solid var(--gt-line);
|
||||
}
|
||||
|
||||
.spin-steps span {
|
||||
font-family: var(--gt-display);
|
||||
font-weight: 600;
|
||||
background: #f5ecd6;
|
||||
color: #a3927c;
|
||||
border-radius: 999px;
|
||||
padding: 2px 16px;
|
||||
|
||||
// Text na žloutku je vždy tmavý (i v tmavém režimu)
|
||||
&.active {
|
||||
background: var(--gt-yolk);
|
||||
color: #4a3520;
|
||||
box-shadow: 0 3px 0 var(--gt-yolk-shadow);
|
||||
}
|
||||
|
||||
&.done {
|
||||
background: #fffdf5;
|
||||
color: #a8650a;
|
||||
box-shadow: inset 0 0 0 2px var(--gt-yolk);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-family: var(--gt-font);
|
||||
font-weight: 800;
|
||||
border-radius: 999px;
|
||||
transition: transform 0.5s ease, box-shadow 0.5s ease, background 0.5s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--gt-yolk);
|
||||
border: none;
|
||||
color: #4a3520;
|
||||
box-shadow: 0 4px 0 var(--gt-yolk-shadow);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #ffdc5c;
|
||||
color: #4a3520;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #efe6cf;
|
||||
color: #a3927c;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Tlačítko roztočení — „…tak jo, no"
|
||||
.spin-button {
|
||||
font-family: var(--gt-display);
|
||||
font-weight: 600;
|
||||
font-size: 1.3rem;
|
||||
padding: 8px 40px;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
background: var(--gt-panel);
|
||||
border: 2px solid #f5b301;
|
||||
color: #a8650a;
|
||||
|
||||
&:hover {
|
||||
background: #fff3c4;
|
||||
color: #a8650a;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6b4a2e;
|
||||
border: none;
|
||||
color: #fffdf5;
|
||||
box-shadow: 0 4px 0 #4a3220;
|
||||
}
|
||||
|
||||
.spin-source {
|
||||
background: #f5ecd6;
|
||||
|
||||
> .btn-primary {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
> .btn-outline-primary {
|
||||
background: transparent;
|
||||
color: var(--gt-ink);
|
||||
|
||||
&:hover {
|
||||
background: var(--gt-panel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spin-result {
|
||||
font-weight: 800;
|
||||
|
||||
strong {
|
||||
color: #a8650a;
|
||||
}
|
||||
}
|
||||
|
||||
.spin-options {
|
||||
background: var(--gt-panel);
|
||||
border: 2px solid var(--gt-line);
|
||||
border-radius: 18px;
|
||||
|
||||
li:hover {
|
||||
background: #fff8dc;
|
||||
}
|
||||
}
|
||||
|
||||
.spin-options-header {
|
||||
border-bottom: 2px solid #f5dc8a;
|
||||
color: #a8650a;
|
||||
font-family: var(--gt-display);
|
||||
}
|
||||
|
||||
.spin-empty {
|
||||
border-color: var(--gt-line);
|
||||
}
|
||||
|
||||
.spin-wheel-pointer {
|
||||
border-top-color: #c8553d;
|
||||
}
|
||||
|
||||
.spin-wheel-hub {
|
||||
stroke: #fffdf5;
|
||||
}
|
||||
|
||||
.spin-seg {
|
||||
stroke: #2b2725;
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
|
||||
.spin-seg-label {
|
||||
font-family: var(--gt-display);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// Tmavý režim — litinová pánev
|
||||
[data-bs-theme="dark"] .spin-wheel-modal.spin-theme-gudetama {
|
||||
--gt-ink: #f5ead2;
|
||||
--gt-line: #3a332e;
|
||||
--gt-panel: #211d1a;
|
||||
--wheel-rim: #3a3430;
|
||||
|
||||
.modal-content {
|
||||
background:
|
||||
radial-gradient(circle at 50% 50%, rgb(255 210 63 / 0.08) 0 120px, transparent 300px),
|
||||
#141210;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background:
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='10'%3E%3Cpath d='M0 3Q12 0 24 3T48 3V7Q36 10 24 7T0 7Z' fill='%23f2a7a0'/%3E%3Cpath d='M0 5Q12 2 24 5T48 5' stroke='%23c8553d' stroke-width='1.5' fill='none'/%3E%3C/svg%3E") left bottom / 48px 10px repeat-x,
|
||||
#0e0d0c;
|
||||
|
||||
h2 {
|
||||
color: #ffd23f;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
filter: invert(1) grayscale(1) brightness(2);
|
||||
}
|
||||
}
|
||||
|
||||
.spin-steps span:not(.active):not(.done) {
|
||||
background: #2b2622;
|
||||
color: #968a7a;
|
||||
}
|
||||
|
||||
.spin-steps span.done {
|
||||
background: #2b2622;
|
||||
color: #ffd23f;
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: #3a332e;
|
||||
color: #968a7a;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
color: #ffd23f;
|
||||
|
||||
&:hover {
|
||||
background: #3a3018;
|
||||
color: #ffe17a;
|
||||
}
|
||||
}
|
||||
|
||||
.spin-source {
|
||||
background: #2b2622;
|
||||
}
|
||||
|
||||
.spin-options li:hover {
|
||||
background: #2b2622;
|
||||
}
|
||||
|
||||
.spin-options-header {
|
||||
color: #ffd23f;
|
||||
border-bottom-color: #4a3d1c;
|
||||
}
|
||||
|
||||
.spin-result strong {
|
||||
color: #ffd23f;
|
||||
}
|
||||
}
|
||||
|
||||
// Ikona kola se při najetí pootočí
|
||||
.spin-wheel-icon {
|
||||
transition: transform 0.4s ease !important;
|
||||
|
||||
@@ -13,7 +13,7 @@ const AUTO_ORDER_CHOICE_KEY = 'auto_order_choice';
|
||||
export type ThemePreference = 'system' | 'light' | 'dark';
|
||||
|
||||
/** Vizuální styl aplikace (nezávislý na světlém/tmavém režimu). */
|
||||
export type StyleTheme = 'simple' | 'umamusume' | 'mlp' | 'hellgirl' | 'kon' | 'bocchi' | 'evangelion' | 'yurucamp' | 'spyfamily' | 'apothecary' | 'frieren' | 'mlpg5' | 'aggretsuko' | 'sumikko';
|
||||
export type StyleTheme = 'simple' | 'umamusume' | 'mlp' | 'hellgirl' | 'kon' | 'bocchi' | 'evangelion' | 'yurucamp' | 'spyfamily' | 'apothecary' | 'frieren' | 'mlpg5' | 'aggretsuko' | 'sumikko' | 'gudetama';
|
||||
|
||||
export const STYLE_THEMES: { value: StyleTheme, label: string }[] = [
|
||||
{ value: 'simple', label: 'Jednoduchý' },
|
||||
@@ -30,6 +30,7 @@ export const STYLE_THEMES: { value: StyleTheme, label: string }[] = [
|
||||
{ value: 'frieren', label: 'Frieren' },
|
||||
{ value: 'aggretsuko', label: 'Aggretsuko' },
|
||||
{ value: 'sumikko', label: 'Sumikko Gurashi' },
|
||||
{ value: 'gudetama', label: 'Gudetama' },
|
||||
];
|
||||
|
||||
export type SettingsContextProps = {
|
||||
@@ -261,7 +262,7 @@ function useProvideSettings(): SettingsContextProps {
|
||||
}, [autoOrderChoice]);
|
||||
|
||||
// Aplikuje accent barvy při změně hue nebo přepnutí světlý/tmavý.
|
||||
// Tematické styly (Umamusume, My Little Pony, Hell Girl, K-ON!, Bocchi the Rock!, Evangelion, Yuru Camp△, Spy × Family, Apothecary Diaries, Frieren, My Little Pony G5, Aggretsuko, Sumikko Gurashi) mají vlastní pevnou paletu, accent barvu proto uplatňuje jen jednoduchý styl.
|
||||
// Tematické styly (Umamusume, My Little Pony, Hell Girl, K-ON!, Bocchi the Rock!, Evangelion, Yuru Camp△, Spy × Family, Apothecary Diaries, Frieren, My Little Pony G5, Aggretsuko, Sumikko Gurashi, Gudetama) mají vlastní pevnou paletu, accent barvu proto uplatňuje jen jednoduchý styl.
|
||||
useEffect(() => {
|
||||
localStorage.setItem(ACCENT_HUE_KEY, String(accentHue));
|
||||
if (styleTheme === 'simple') {
|
||||
|
||||
@@ -31,6 +31,8 @@ const ICONS: Record<StyleTheme, Record<GameId, string>> = {
|
||||
aggretsuko: { drawing: '🖊️' },
|
||||
// Tužka, kterou se kreslí postavičky ze San-X
|
||||
sumikko: { drawing: '✏️' },
|
||||
// Vajíčko — kreslit se mu ale stejně nechce
|
||||
gudetama: { drawing: '🥚' },
|
||||
};
|
||||
|
||||
/** Styl z <html data-luncher-theme> — pro místa mimo React kontext nastavení. */
|
||||
|
||||
@@ -0,0 +1,539 @@
|
||||
// ============================================
|
||||
// STYL "GUDETAMA" (volitelný, výchozí je jednoduchý styl)
|
||||
// Inspirováno líným vajíčkem Gudetama (ぐでたま) od Sanria.
|
||||
// Kuchyň s bílými kachličkami, žloutkově žlutá, bílek, proužek slaniny
|
||||
// (Gudetamova oblíbená deka) a titulek dne jako volské oko, které se
|
||||
// občas líně „rozteče". Všechno je trochu líné — i přechody tlačítek.
|
||||
// Tmavý režim = litinová pánev, ve které svítí žloutek.
|
||||
// Aktivní, pokud má <html> atribut data-luncher-theme="gudetama".
|
||||
// ============================================
|
||||
|
||||
$gt: 'html[data-luncher-theme="gudetama"]';
|
||||
|
||||
// Proužek slaniny — vlnitý růžový pruh s tmavší žilkou, opakuje se vodorovně
|
||||
$gt-bacon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='10'%3E%3Cpath d='M0 3Q12 0 24 3T48 3V7Q36 10 24 7T0 7Z' fill='%23f2a7a0'/%3E%3Cpath d='M0 5Q12 2 24 5T48 5' stroke='%23c8553d' stroke-width='1.5' fill='none'/%3E%3C/svg%3E");
|
||||
|
||||
#{$gt} {
|
||||
--gt-font: 'Nunito', -apple-system, sans-serif;
|
||||
--gt-display: 'Fredoka', 'Nunito', sans-serif;
|
||||
--gt-yolk: #ffd23f; // žloutek
|
||||
--gt-yolk-deep: #f5b301;
|
||||
--gt-yolk-shadow: #d99a00;
|
||||
--gt-white: #fffdf5; // bílek
|
||||
--gt-bacon: #c8553d;
|
||||
--gt-soy: #6b4a2e; // sójová omáčka
|
||||
--gt-ink: #4a3520;
|
||||
--gt-grout: rgb(74 53 32 / 0.06);
|
||||
--gt-line: #efe3c4;
|
||||
|
||||
--luncher-primary: #a8650a;
|
||||
--luncher-primary-hover: #8a5208;
|
||||
--luncher-primary-light: #fff3c4;
|
||||
--luncher-bg: #fbf8f1;
|
||||
--luncher-bg-card: #ffffff;
|
||||
--luncher-bg-hover: #fff8dc;
|
||||
--luncher-text: var(--gt-ink);
|
||||
--luncher-text-secondary: #6e5a44;
|
||||
--luncher-text-muted: #a3927c;
|
||||
--luncher-border: var(--gt-line);
|
||||
--luncher-border-light: #f5ecd6;
|
||||
--luncher-navbar-bg: var(--gt-yolk);
|
||||
--luncher-navbar-text: var(--gt-ink);
|
||||
--luncher-action-icon: #a8650a;
|
||||
--luncher-buyer-icon: var(--gt-bacon);
|
||||
--luncher-success: #5a9a4a;
|
||||
--luncher-warning: #e08a00;
|
||||
--luncher-danger: var(--gt-bacon);
|
||||
--luncher-radius-sm: 12px;
|
||||
--luncher-radius: 16px;
|
||||
--luncher-radius-lg: 20px;
|
||||
--luncher-radius-xl: 24px;
|
||||
--luncher-shadow: 0 4px 0 rgb(217 154 0 / 0.12), 0 8px 18px rgb(74 53 32 / 0.08);
|
||||
--luncher-shadow-lg: 0 6px 0 rgb(217 154 0 / 0.18), 0 14px 28px rgb(74 53 32 / 0.14);
|
||||
|
||||
&[data-bs-theme="dark"] {
|
||||
// Litinová pánev — žloutek svítí ve tmě
|
||||
--gt-white: #2a2522;
|
||||
--gt-ink: #f5ead2;
|
||||
--gt-line: #3a332e;
|
||||
|
||||
--luncher-primary: #ffd23f;
|
||||
--luncher-primary-hover: #ffe17a;
|
||||
--luncher-primary-light: #3a3018;
|
||||
--luncher-bg: #141210;
|
||||
--luncher-bg-card: #211d1a;
|
||||
--luncher-bg-hover: #2b2622;
|
||||
--luncher-text: var(--gt-ink);
|
||||
--luncher-text-secondary: #d3c6ae;
|
||||
--luncher-text-muted: #968a7a;
|
||||
--luncher-border-light: #2a2522;
|
||||
--luncher-navbar-bg: #0e0d0c;
|
||||
--luncher-navbar-text: #ffd23f;
|
||||
--luncher-action-icon: #ffd23f;
|
||||
--luncher-buyer-icon: #f2a7a0;
|
||||
--luncher-success: #9ccf86;
|
||||
--luncher-warning: #ffc04d;
|
||||
--luncher-danger: #f08a7a;
|
||||
--luncher-shadow: 0 4px 14px rgb(0 0 0 / 0.4);
|
||||
--luncher-shadow-lg: 0 10px 28px rgb(255 210 63 / 0.14);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--gt-font);
|
||||
background-color: var(--luncher-bg);
|
||||
// Kuchyňské kachličky se spárami, nahoře teplé světlo
|
||||
background-image:
|
||||
linear-gradient(var(--gt-grout) 1.5px, transparent 1.5px),
|
||||
linear-gradient(90deg, var(--gt-grout) 1.5px, transparent 1.5px),
|
||||
linear-gradient(180deg, #fff6d6 0, var(--luncher-bg) 360px);
|
||||
background-size: 46px 46px, 46px 46px, 100% 100%;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] body {
|
||||
// Dno litinové pánve s okrajem a teplou září žloutku
|
||||
background-image:
|
||||
radial-gradient(circle at 50% 45%, rgb(255 210 63 / 0.07) 0 160px, transparent 360px),
|
||||
radial-gradient(circle at 50% 45%, #1d1a17 0 min(46vw, 560px), #2e2925 calc(min(46vw, 560px) + 1px) calc(min(46vw, 560px) + 14px), transparent calc(min(46vw, 560px) + 15px));
|
||||
}
|
||||
|
||||
.app-container {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, .navbar-brand {
|
||||
font-family: var(--gt-display);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-family: var(--gt-font);
|
||||
}
|
||||
|
||||
// ---------- Navbar: žloutek s proužkem slaniny ----------
|
||||
.navbar {
|
||||
background:
|
||||
#{$gt-bacon} left bottom / 48px 10px repeat-x,
|
||||
linear-gradient(180deg, #ffdc5c, var(--luncher-navbar-bg)) !important;
|
||||
padding-bottom: calc(var(--bs-navbar-padding-y, 0.5rem) + 10px);
|
||||
border-bottom: none;
|
||||
|
||||
.navbar-brand {
|
||||
// Vajíčko na pánvi
|
||||
&::after {
|
||||
content: '🍳';
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
margin-right: 0.6em;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
// Navbar je ve variantě „dark" — ikonu hamburgeru na žlutém pozadí ztmavíme
|
||||
.navbar-toggler {
|
||||
border-color: rgb(74 53 32 / 0.35);
|
||||
}
|
||||
|
||||
.navbar-toggler-icon {
|
||||
filter: invert(1) brightness(0.3);
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
border: 2px solid var(--gt-line);
|
||||
border-radius: var(--luncher-radius);
|
||||
box-shadow: var(--luncher-shadow-lg);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] .navbar {
|
||||
background:
|
||||
#{$gt-bacon} left bottom / 48px 10px repeat-x,
|
||||
var(--luncher-navbar-bg) !important;
|
||||
|
||||
.navbar-toggler {
|
||||
border-color: rgb(255 210 63 / 0.4);
|
||||
}
|
||||
|
||||
.navbar-toggler-icon {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Titulek dne: volské oko — bílek, vedle žloutek, občas se rozteče ----------
|
||||
.day-navigator .title {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 10px 34px 12px 70px;
|
||||
font-family: var(--gt-display);
|
||||
font-weight: 600;
|
||||
font-size: 1.65rem;
|
||||
color: #4a3520 !important;
|
||||
background: #fffdf5;
|
||||
// Nepravidelný tvar bílku
|
||||
border-radius: 58% 42% 55% 45% / 60% 55% 45% 40%;
|
||||
box-shadow: 0 4px 0 #efe3c4, 0 10px 22px rgb(74 53 32 / 0.14);
|
||||
transform-origin: 50% 100%;
|
||||
animation: gt-gude 7s ease-in-out infinite;
|
||||
|
||||
// Žloutek (Gudetama) s odleskem
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 50%;
|
||||
width: 38px;
|
||||
height: 32px;
|
||||
margin-top: -15px;
|
||||
border-radius: 50% 50% 46% 46% / 58% 58% 42% 42%;
|
||||
background: radial-gradient(circle at 34% 30%, #fff6c8 0 4px, #ffd23f 6px, #f5b301 100%);
|
||||
box-shadow: 0 2px 3px rgb(217 154 0 / 0.5);
|
||||
}
|
||||
|
||||
// Líné „zzz"
|
||||
&::after {
|
||||
content: '💤';
|
||||
position: absolute;
|
||||
left: 46px;
|
||||
top: -12px;
|
||||
font-size: 0.55em;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&.text-muted {
|
||||
color: #8a7a64 !important;
|
||||
background: #f3efe6;
|
||||
animation: none;
|
||||
|
||||
// Jiný den — vystydlé vajíčko
|
||||
&::before {
|
||||
background: radial-gradient(circle at 34% 30%, #fbf3d8 0 4px, #e8d8a0 6px, #d9c27a 100%);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] .day-navigator .title {
|
||||
box-shadow: 0 4px 0 #d9ccb0, 0 0 30px rgb(255 210 63 / 0.18);
|
||||
|
||||
&.text-muted {
|
||||
background: #d9d2c2;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Karty podniků: žloutková hlavička, při najetí se líně prohne ----------
|
||||
.restaurant-card {
|
||||
border: 2px solid var(--gt-line);
|
||||
border-radius: var(--luncher-radius-lg);
|
||||
box-shadow: var(--luncher-shadow);
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--gt-line);
|
||||
box-shadow: var(--luncher-shadow-lg);
|
||||
}
|
||||
|
||||
.restaurant-header {
|
||||
// Lesklý žloutek
|
||||
background:
|
||||
radial-gradient(ellipse 40% 60% at 12% 20%, rgb(255 255 255 / 0.45), transparent 70%),
|
||||
linear-gradient(180deg, #ffdc5c, var(--gt-yolk-deep));
|
||||
border-bottom: 3px solid var(--gt-yolk-shadow);
|
||||
border-radius: 0 0 26px 26px / 0 0 10px 10px;
|
||||
transform-origin: 50% 0;
|
||||
transition: transform 0.8s ease, border-radius 0.8s ease, background 0.6s ease;
|
||||
|
||||
// Gude~ — hlavička se líně prohne a „rozteče"
|
||||
&:hover {
|
||||
background:
|
||||
radial-gradient(ellipse 40% 60% at 12% 20%, rgb(255 255 255 / 0.45), transparent 70%),
|
||||
linear-gradient(180deg, #ffd23f, #eba300);
|
||||
border-radius: 0 0 60% 40% / 0 0 22px 14px;
|
||||
transform: scaleY(1.04);
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #4a3520;
|
||||
}
|
||||
|
||||
small {
|
||||
color: rgb(74 53 32 / 0.7);
|
||||
}
|
||||
|
||||
.restaurant-warning {
|
||||
color: var(--gt-bacon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.food-table tbody tr:hover > td {
|
||||
background: #fff8dc;
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] .food-table tbody tr:hover > td {
|
||||
background: #2b2622;
|
||||
}
|
||||
|
||||
.food-price {
|
||||
color: #a8650a;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] .food-price {
|
||||
color: var(--gt-yolk);
|
||||
}
|
||||
|
||||
// ---------- Panely a tabulka voleb ----------
|
||||
.choice-section,
|
||||
.choices-table,
|
||||
.pizza-section,
|
||||
.no-votes {
|
||||
border: 2px solid var(--gt-line);
|
||||
border-radius: var(--luncher-radius-lg);
|
||||
box-shadow: var(--luncher-shadow);
|
||||
}
|
||||
|
||||
.choices-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
// Sloupec s podnikem: světlý žloutek
|
||||
.choices-table > tbody > tr > td:first-child {
|
||||
background: #fff3c4;
|
||||
color: #4a3520;
|
||||
font-family: var(--gt-display);
|
||||
font-weight: 600;
|
||||
border-right: 2px solid #f5dc8a;
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] .choices-table > tbody > tr > td:first-child {
|
||||
background: #3a3018;
|
||||
color: var(--gt-yolk);
|
||||
border-right-color: #4a3d1c;
|
||||
}
|
||||
|
||||
// Bootstrap karty (skupinové objednávky apod.) a seznamy
|
||||
.card {
|
||||
border: 2px solid var(--gt-line);
|
||||
border-radius: var(--luncher-radius-lg);
|
||||
box-shadow: var(--luncher-shadow);
|
||||
overflow: hidden;
|
||||
background: var(--luncher-bg-card);
|
||||
|
||||
// Rozbalovací výběr z nabídky nesmí karta oříznout
|
||||
&:has(.select-search-container) {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
> .card-header {
|
||||
background: var(--luncher-primary-light);
|
||||
border-bottom: 2px solid #f5dc8a;
|
||||
font-weight: 700;
|
||||
|
||||
&:first-child {
|
||||
border-radius: calc(var(--luncher-radius-lg) - 2px) calc(var(--luncher-radius-lg) - 2px) 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.table {
|
||||
--bs-table-bg: transparent;
|
||||
margin-bottom: 0;
|
||||
|
||||
> :last-child > tr:last-child > * {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-group {
|
||||
border-radius: var(--luncher-radius);
|
||||
overflow: hidden;
|
||||
border: 2px solid var(--gt-line);
|
||||
|
||||
> .list-group-item {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-select, .form-control,
|
||||
input[type="text"], input[type="email"], input[type="password"] {
|
||||
border-radius: var(--luncher-radius-sm);
|
||||
border: 2px solid var(--gt-line);
|
||||
|
||||
&:focus {
|
||||
border-color: var(--gt-yolk-deep);
|
||||
box-shadow: 0 0 0 4px rgb(255 210 63 / 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Tlačítka: žloutková a buclatá, s líným přechodem ----------
|
||||
.btn {
|
||||
border-radius: 999px;
|
||||
font-weight: 800;
|
||||
transition: transform 0.5s ease, box-shadow 0.5s ease, background 0.5s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--gt-yolk);
|
||||
border: none;
|
||||
color: #4a3520;
|
||||
box-shadow: 0 4px 0 var(--gt-yolk-shadow);
|
||||
|
||||
&:hover {
|
||||
background: #ffdc5c;
|
||||
color: #4a3520;
|
||||
box-shadow: 0 4px 0 var(--gt-yolk-shadow), 0 0 0 4px rgb(255 210 63 / 0.3);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(3px) scaleY(0.94) !important;
|
||||
box-shadow: 0 1px 0 var(--gt-yolk-shadow) !important;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #efe6cf;
|
||||
color: #a3927c;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] .btn-primary:disabled {
|
||||
background: #3a332e;
|
||||
color: #968a7a;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
background: var(--luncher-bg-card);
|
||||
border: 2px solid var(--gt-yolk-deep);
|
||||
color: var(--luncher-primary);
|
||||
|
||||
&:hover {
|
||||
background: var(--luncher-primary-light);
|
||||
border-color: var(--gt-yolk-deep);
|
||||
color: var(--luncher-primary);
|
||||
}
|
||||
}
|
||||
|
||||
// Sekundární — sójová omáčka
|
||||
.btn-secondary {
|
||||
background: var(--gt-soy);
|
||||
border: none;
|
||||
color: #fffdf5;
|
||||
box-shadow: 0 4px 0 #4a3220;
|
||||
|
||||
&:hover {
|
||||
background: #7d5a3a;
|
||||
color: #fffdf5;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--gt-bacon);
|
||||
border: none;
|
||||
box-shadow: 0 4px 0 #9a3a28;
|
||||
}
|
||||
|
||||
.action-icon:hover {
|
||||
background: var(--luncher-primary-light);
|
||||
color: var(--luncher-primary);
|
||||
}
|
||||
|
||||
// ---------- Modaly: žloutek nahoře, proužek slaniny ----------
|
||||
.modal-content {
|
||||
border: 2px solid var(--gt-line);
|
||||
border-radius: var(--luncher-radius-xl);
|
||||
box-shadow: 0 16px 40px rgb(74 53 32 / 0.25);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background:
|
||||
#{$gt-bacon} left bottom / 48px 10px repeat-x,
|
||||
linear-gradient(180deg, #ffdc5c, var(--gt-yolk));
|
||||
border-bottom: none;
|
||||
padding-bottom: calc(var(--bs-modal-header-padding-y, 1rem) + 8px);
|
||||
|
||||
.modal-title, .modal-title h2 {
|
||||
color: #4a3520;
|
||||
font-family: var(--gt-display);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] .modal-header {
|
||||
background:
|
||||
#{$gt-bacon} left bottom / 48px 10px repeat-x,
|
||||
#0e0d0c;
|
||||
|
||||
.modal-title, .modal-title h2 {
|
||||
color: var(--gt-yolk);
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
filter: invert(1) grayscale(1) brightness(2);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: none;
|
||||
background-image: $gt-bacon;
|
||||
background-size: 48px 10px;
|
||||
background-repeat: repeat-x;
|
||||
background-position: top;
|
||||
|
||||
a {
|
||||
color: #8a3a28;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] .footer a {
|
||||
color: #f2a7a0;
|
||||
}
|
||||
}
|
||||
|
||||
// Gude~ — vajíčko se občas líně „rozteče" a zase se sebere
|
||||
@keyframes gt-gude {
|
||||
0%, 78%, 100% { transform: scale(1, 1); }
|
||||
84% { transform: scale(1.04, 0.92); }
|
||||
90% { transform: scale(1.06, 0.9); }
|
||||
95% { transform: scale(1.01, 0.98); }
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
#{$gt} .day-navigator .title {
|
||||
font-size: 1.3rem;
|
||||
padding: 8px 22px 10px 56px;
|
||||
|
||||
&::before {
|
||||
left: 14px;
|
||||
width: 30px;
|
||||
height: 26px;
|
||||
margin-top: -12px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
left: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#{$gt} .day-navigator .title {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user