feat: přidání vzhledu Hell Girl
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 27s
CI / Build server (push) Successful in 30s
CI / Build client (push) Successful in 42s
CI / Playwright E2E tests (push) Successful in 1m51s
CI / Build and push Docker image (push) Successful in 46s
CI / Notify (push) Successful in 1s
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 27s
CI / Build server (push) Successful in 30s
CI / Build client (push) Successful in 42s
CI / Playwright E2E tests (push) Successful in 1m51s
CI / Build and push Docker image (push) Successful in 46s
CI / Notify (push) Successful in 1s
This commit is contained in:
9 files changed
+789
-22
No files matched your search
+3
-2
@@ -11,7 +11,7 @@
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@500;800&family=Nunito:wght@600;800;900&family=Fredoka:wght@500;600;700&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@500;800&family=Nunito:wght@600;800;900&family=Fredoka:wght@500;600;700&family=Shippori+Mincho:wght@600;700;800&display=swap" rel="stylesheet" />
|
||||
<title>Luncher</title>
|
||||
<script>
|
||||
(function() {
|
||||
@@ -28,7 +28,8 @@
|
||||
}
|
||||
document.documentElement.setAttribute('data-bs-theme', theme);
|
||||
var style = localStorage.getItem('style_theme');
|
||||
document.documentElement.setAttribute('data-luncher-theme', style === 'umamusume' ? 'umamusume' : 'simple');
|
||||
var styles = ['umamusume', 'mlp', 'hellgirl'];
|
||||
document.documentElement.setAttribute('data-luncher-theme', styles.indexOf(style) >= 0 ? style : 'simple');
|
||||
} catch (e) {
|
||||
// Fallback pokud localStorage není dostupný
|
||||
document.documentElement.setAttribute('data-bs-theme', 'light');
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'react-select-search/style.css';
|
||||
import './App.scss';
|
||||
import './themes/umamusume.scss';
|
||||
import './themes/mlp.scss';
|
||||
import './themes/hellgirl.scss';
|
||||
import { faCircleCheck, faNoteSticky, faTrashCan, faComment } from '@fortawesome/free-regular-svg-icons';
|
||||
import { useSettings } from './context/settings';
|
||||
import Footer from './components/Footer';
|
||||
|
||||
@@ -29,6 +29,17 @@
|
||||
box-shadow: 0 0 0 2px #8e4fd1;
|
||||
}
|
||||
|
||||
// Styl Hell Girl: emoji v černém odznaku s rudým lemem (rudá nit)
|
||||
.eater-badge-hellgirl {
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
border-color: #1c1012;
|
||||
background: radial-gradient(circle at 50% 60%, #3a0d16, #12070a);
|
||||
box-shadow: 0 0 0 2px #c8102e, 0 0 6px rgb(200 16 46 / 0.5);
|
||||
}
|
||||
|
||||
// Odznak a jméno drží na jednom řádku (úzký sloupec „Člen" na stránce objednávek)
|
||||
.user-info:has(.eater-badge) {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -19,12 +19,18 @@ const MLP_LEVELS = [
|
||||
{ minItems: 2, emoji: '🍎', title: 'Úroveň Applejack — poctivý apetit' },
|
||||
];
|
||||
|
||||
/** Úrovně ve stylu Hell Girl — emoji v černém odznaku s rudým lemem. Seřazeno od nejvyšší úrovně. */
|
||||
const HG_LEVELS = [
|
||||
{ minItems: 3, emoji: '🔥', title: 'Úroveň Wanyūdō — hlad jako pekelný oheň' },
|
||||
{ minItems: 2, emoji: '🥀', title: 'Úroveň Ai Enma — tichý, ale nenasytný apetit' },
|
||||
];
|
||||
|
||||
type Props = {
|
||||
/** Počet jídel/položek, které si strávník vybral či objednal */
|
||||
items: number,
|
||||
};
|
||||
|
||||
/** Odznak s postavou u strávníků s více jídly — jen ve stylech Umamusume a My Little Pony. */
|
||||
/** Odznak s postavou u strávníků s více jídly — jen ve stylech Umamusume, My Little Pony a Hell Girl. */
|
||||
export default function EaterBadge({ items }: Readonly<Props>) {
|
||||
const styleTheme = useSettings()?.styleTheme;
|
||||
const isUma = styleTheme === 'umamusume';
|
||||
@@ -34,12 +40,12 @@ export default function EaterBadge({ items }: Readonly<Props>) {
|
||||
if (isUma) loadUmaCharacters().then(setCharacters);
|
||||
}, [isUma]);
|
||||
|
||||
if (styleTheme === 'mlp') {
|
||||
const mlpLevel = MLP_LEVELS.find(l => items >= l.minItems);
|
||||
if (!mlpLevel) return null;
|
||||
if (styleTheme === 'mlp' || styleTheme === 'hellgirl') {
|
||||
const emojiLevel = (styleTheme === 'mlp' ? MLP_LEVELS : HG_LEVELS).find(l => items >= l.minItems);
|
||||
if (!emojiLevel) return null;
|
||||
return (
|
||||
<OverlayTrigger placement="top" overlay={<Tooltip>{mlpLevel.title} ({items} jídla)</Tooltip>}>
|
||||
<span className="eater-badge eater-badge-mlp" role="img" aria-label={mlpLevel.title}>{mlpLevel.emoji}</span>
|
||||
<OverlayTrigger placement="top" overlay={<Tooltip>{emojiLevel.title} ({items} jídla)</Tooltip>}>
|
||||
<span className={`eater-badge eater-badge-${styleTheme}`} role="img" aria-label={emojiLevel.title}>{emojiLevel.emoji}</span>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -260,3 +260,69 @@
|
||||
border-right: 10px solid var(--mlp-speaker, #8e4fd1);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Konfety ve stylu Hell Girl
|
||||
// ============================================
|
||||
.confetti-hg {
|
||||
filter: drop-shadow(0 0 6px rgb(200 16 46 / 0.55)) drop-shadow(0 3px 3px rgb(0 0 0 / 0.3));
|
||||
}
|
||||
|
||||
.confetti-hg-badge {
|
||||
padding: 4px 14px;
|
||||
border: 1px solid #c8102e;
|
||||
outline: 1px solid rgb(200 16 46 / 0.4);
|
||||
outline-offset: 2px;
|
||||
color: #fff4ee;
|
||||
font-family: 'Shippori Mincho', 'Noto Serif JP', serif;
|
||||
font-weight: 800;
|
||||
font-size: 1.1rem;
|
||||
letter-spacing: 1px;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 0 8px rgb(232 52 79 / 0.7);
|
||||
}
|
||||
|
||||
// Nápis „poslání do pekla" — černá deska s rudým lemem, vynoří se z mlhy
|
||||
.confetti-hg-banner {
|
||||
position: absolute;
|
||||
top: 38%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 14px 48px;
|
||||
font-family: 'Shippori Mincho', 'Noto Serif JP', serif;
|
||||
font-weight: 800;
|
||||
font-size: clamp(2rem, 7vw, 4rem);
|
||||
letter-spacing: 0.3em;
|
||||
white-space: nowrap;
|
||||
color: #fff4ee;
|
||||
background: linear-gradient(90deg, transparent, #12070a 15%, #12070a 85%, transparent);
|
||||
border-top: 2px solid #c8102e;
|
||||
border-bottom: 2px solid #c8102e;
|
||||
text-shadow: 0 0 18px rgb(232 52 79 / 0.9), 0 0 4px #c8102e;
|
||||
animation: hg-banner 2.6s ease both;
|
||||
}
|
||||
|
||||
@keyframes hg-banner {
|
||||
0% { transform: translate(-50%, -50%) scale(1.15); opacity: 0; filter: blur(12px); }
|
||||
25% { transform: translate(-50%, -50%) scale(1); opacity: 1; filter: blur(0); }
|
||||
75% { transform: translate(-50%, -50%) scale(1); opacity: 1; filter: blur(0); }
|
||||
100% { transform: translate(-50%, -50%) scale(0.96); opacity: 0; filter: blur(8px); }
|
||||
}
|
||||
|
||||
// Postava s hláškou — tmavý odznak s rudou září a bublina jako svitek
|
||||
.confetti-hellgirl-voice {
|
||||
.confetti-mlp-avatar {
|
||||
border-color: #12070a;
|
||||
background: radial-gradient(circle at 50% 60%, #3a0d16, #12070a);
|
||||
box-shadow: 0 0 0 3px var(--mlp-speaker, #c8102e), 0 0 18px rgb(200 16 46 / 0.6);
|
||||
}
|
||||
|
||||
.confetti-mlp-bubble {
|
||||
background: #170c0f;
|
||||
color: #f3e6e2;
|
||||
border: 1px solid var(--mlp-speaker, #c8102e);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 16px rgb(0 0 0 / 0.6), 0 0 10px rgb(200 16 46 / 0.3);
|
||||
font-family: 'Shippori Mincho', 'Noto Serif JP', serif;
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,19 @@ const MLP_SPEAKERS = [
|
||||
{ name: 'Rarity', emoji: '💎', color: '#5b4fc9', quote: 'Naprosto božská volba, drahoušku!' },
|
||||
{ name: 'Spike', emoji: '🐉', color: '#62bc4d', quote: 'Zapsáno do svitku a odesláno princezně!' },
|
||||
];
|
||||
const HG_EMOJI = ['🥀', '🏮', '⛩️', '🔥', '🕯️', '🌑', '🦋', '👁️', '🕸️', '🌸'];
|
||||
// Hlášky z Jigoku Shōjo (Pekelná korespondence, „Ippen, shinde miru?", poslání do pekla...)
|
||||
const HG_BADGES = ['いっぺん、死んでみる?', '地獄通信', '地獄流し', 'Ippen… shinde miru?', 'Pomsta vykonána', '0:00', 'Uvaž to dobře…', 'Smlouva uzavřena'];
|
||||
const HG_BADGE_COLORS = ['#12070a', '#7a0c1f', '#2e0d24'];
|
||||
/** Ai Enma a její služebníci s „hláškou" po finálním výsledku — místo obrázků emoji. */
|
||||
const HG_SPEAKERS = [
|
||||
{ name: 'Ai Enma', emoji: '🥀', color: '#c8102e', quote: 'Smlouva je uzavřena. Tenhle oběd tě bude provázet až na věčnost.' },
|
||||
{ name: 'Ichimoku Ren', emoji: '👁️', color: '#6b5a8a', quote: 'Prohlédl jsem svým okem všechny podniky. Tenhle jediný obstál.' },
|
||||
{ name: 'Hone Onna', emoji: '💀', color: '#b8862b', quote: 'Dobré až na kost, zlato. Mně z toho stejně nic nezůstane.' },
|
||||
{ name: 'Wanyūdō', emoji: '🔥', color: '#d9582b', quote: 'Já jsem kolo. Kolo rozhodlo. Víc k tomu nemám.' },
|
||||
{ name: 'Kikuri', emoji: '🎀', color: '#d4407a', quote: 'Hihi! Tohle bych do pekla poslala jako první!' },
|
||||
{ name: 'Babička', emoji: '🕸️', color: '#8c7672', quote: 'Ai, oběd je na stole… jako každý den už čtyři sta let.' },
|
||||
];
|
||||
const DURATION_MS = 6400;
|
||||
|
||||
const rand = (min: number, max: number) => min + Math.random() * (max - min);
|
||||
@@ -121,7 +134,10 @@ function buildPieces(theme: WheelTheme, big: boolean, images: UmaImage[]): Piece
|
||||
}));
|
||||
}
|
||||
const count = big ? 50 : 24;
|
||||
if (theme === 'mlp') {
|
||||
if (theme === 'mlp' || theme === 'hellgirl') {
|
||||
const [prefix, badges, colors, emoji] = theme === 'mlp'
|
||||
? ['confetti-mlp', MLP_BADGES, MLP_BADGE_COLORS, MLP_EMOJI]
|
||||
: ['confetti-hg', HG_BADGES, HG_BADGE_COLORS, HG_EMOJI];
|
||||
return Array.from({ length: count }, (_, id) => {
|
||||
const base: React.CSSProperties = {
|
||||
...launchStyle(pick(LAUNCHES)),
|
||||
@@ -129,10 +145,10 @@ function buildPieces(theme: WheelTheme, big: boolean, images: UmaImage[]): Piece
|
||||
['--r2' as any]: `${rand(-40, 40)}deg`,
|
||||
};
|
||||
if (Math.random() < 0.35) {
|
||||
return { id, className: 'confetti-mlp confetti-mlp-badge', content: pick(MLP_BADGES),
|
||||
style: { ...base, background: pick(MLP_BADGE_COLORS) } };
|
||||
return { id, className: `${prefix} ${prefix}-badge`, content: pick(badges),
|
||||
style: { ...base, background: pick(colors) } };
|
||||
}
|
||||
return { id, className: 'confetti-mlp confetti-uma-emoji', style: { ...base, fontSize: `${rand(28, 52)}px` }, content: pick(MLP_EMOJI) };
|
||||
return { id, className: `${prefix} confetti-uma-emoji`, style: { ...base, fontSize: `${rand(28, 52)}px` }, content: pick(emoji) };
|
||||
});
|
||||
}
|
||||
return Array.from({ length: count }, (_, id) => {
|
||||
@@ -185,9 +201,9 @@ export default function WheelConfetti({ theme, burstKey, big = false }: Readonly
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [burstKey]);
|
||||
|
||||
// MLP: hlášku pronese náhodná postava z Mane 6
|
||||
const mlpSpeaker = useMemo(
|
||||
() => theme === 'mlp' && big ? pick(MLP_SPEAKERS) : undefined,
|
||||
// MLP: hlášku pronese náhodná postava z Mane 6, Hell Girl: Ai Enma či některý z jejích služebníků
|
||||
const emojiSpeaker = useMemo(
|
||||
() => big ? (theme === 'mlp' ? pick(MLP_SPEAKERS) : theme === 'hellgirl' ? pick(HG_SPEAKERS) : undefined) : undefined,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[burstKey],
|
||||
);
|
||||
@@ -198,6 +214,7 @@ export default function WheelConfetti({ theme, burstKey, big = false }: Readonly
|
||||
<div className={`wheel-confetti wheel-confetti-${theme}`} key={burstKey} aria-hidden>
|
||||
{theme === 'umamusume' && big && <div className="confetti-uma-banner">🏆 1着 🏆</div>}
|
||||
{theme === 'mlp' && big && <div className="confetti-mlp-banner">🌈 Sonic Rainboom! 🌈</div>}
|
||||
{theme === 'hellgirl' && big && <div className="confetti-hg-banner">🏮 地獄流し 🏮</div>}
|
||||
{pieces.map(p => <span key={p.id} className={p.className} style={p.style}>{p.content}</span>)}
|
||||
{speaker && (
|
||||
<div className="confetti-uma-voice">
|
||||
@@ -208,12 +225,12 @@ export default function WheelConfetti({ theme, burstKey, big = false }: Readonly
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{mlpSpeaker && (
|
||||
<div className="confetti-uma-voice confetti-mlp-voice" style={{ ['--mlp-speaker' as any]: mlpSpeaker.color }}>
|
||||
<span className="confetti-mlp-avatar">{mlpSpeaker.emoji}</span>
|
||||
{emojiSpeaker && (
|
||||
<div className={`confetti-uma-voice confetti-${theme}-voice`} style={{ ['--mlp-speaker' as any]: emojiSpeaker.color }}>
|
||||
<span className="confetti-mlp-avatar">{emojiSpeaker.emoji}</span>
|
||||
<div className="confetti-mlp-bubble">
|
||||
<p>„{mlpSpeaker.quote}"</p>
|
||||
<small>— {mlpSpeaker.name}</small>
|
||||
<p>„{emojiSpeaker.quote}"</p>
|
||||
<small>— {emojiSpeaker.name}</small>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -774,6 +774,245 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// STYL HELL GIRL kola štěstí (dle stylu zvoleného v nastavení).
|
||||
// Výseče v barvách soumraku a pekla, rudá nit jako lem,
|
||||
// černá tuš, papír washi a ostrá tlačítka s písmem mincho.
|
||||
// ============================================
|
||||
.spin-wheel-modal.spin-theme-hellgirl {
|
||||
--hg-font: 'Shippori Mincho', 'Noto Serif JP', 'Georgia', serif;
|
||||
--hg-crimson: #a3122a;
|
||||
--hg-crimson-deep: #7a0c1f;
|
||||
--hg-crimson-shadow: #4a0612;
|
||||
--hg-panel: #fffbf5;
|
||||
--hg-border: #dccbbb;
|
||||
--hg-text: #1c1012;
|
||||
--wheel-c0: #a3122a;
|
||||
--wheel-c1: #1f1416;
|
||||
--wheel-c2: #c8452a;
|
||||
--wheel-c3: #4b1a4f;
|
||||
--wheel-c4: #6e0b1a;
|
||||
--wheel-c5: #9a6b1f;
|
||||
--wheel-rim: #c8102e;
|
||||
--wheel-hub: #12070a;
|
||||
|
||||
font-family: var(--hg-font);
|
||||
|
||||
.modal-content {
|
||||
border: 1px solid var(--hg-crimson);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(180deg, #f3d9cc, #f4ece0 220px);
|
||||
color: var(--hg-text);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background:
|
||||
linear-gradient(90deg, transparent, #c8102e 12%, #e8344f 50%, #c8102e 88%, transparent) bottom / 100% 2px no-repeat,
|
||||
linear-gradient(90deg, #1e0a1c, #4a0f2a 35%, #8e0f24 70%, #c8452a);
|
||||
border-bottom: none;
|
||||
|
||||
h2 {
|
||||
color: #fff4ee;
|
||||
font-family: var(--hg-font);
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.5px;
|
||||
text-shadow: 0 0 10px rgb(232 52 79 / 0.5);
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
filter: invert(1) grayscale(1) brightness(2);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border-top: 1px solid var(--hg-border);
|
||||
}
|
||||
|
||||
.spin-steps span {
|
||||
font-family: var(--hg-font);
|
||||
background: #eadfd2;
|
||||
color: #8c7672;
|
||||
border-radius: 2px;
|
||||
padding: 4px 16px;
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(90deg, #1c0a10, var(--hg-crimson-deep));
|
||||
color: #fff4ee;
|
||||
}
|
||||
|
||||
&.done {
|
||||
background: var(--hg-crimson);
|
||||
color: #fff4ee;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-family: var(--hg-font);
|
||||
font-weight: 700;
|
||||
border-radius: 2px;
|
||||
transition: transform 0.12s ease, box-shadow 0.2s ease, background 0.2s ease;
|
||||
|
||||
&:active:not(:disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(180deg, #c01733, var(--hg-crimson-deep));
|
||||
border: 1px solid var(--hg-crimson-shadow);
|
||||
color: #fff4ee;
|
||||
box-shadow: inset 0 0 0 2px rgb(255 255 255 / 0.12);
|
||||
|
||||
&:disabled {
|
||||
background: #b9a3a0;
|
||||
border-color: #9c8582;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Tlačítko roztočení — černá deska s rudou září
|
||||
.spin-button {
|
||||
background: linear-gradient(180deg, #2a1216, #12070a);
|
||||
border: 1px solid #c8102e;
|
||||
box-shadow: 0 0 0 3px rgb(200 16 46 / 0.25), 0 0 18px rgb(200 16 46 / 0.45);
|
||||
letter-spacing: 1px;
|
||||
font-size: 1.15rem;
|
||||
padding: 12px 36px;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: linear-gradient(180deg, #3a1219, #1c0a10);
|
||||
box-shadow: 0 0 0 3px rgb(200 16 46 / 0.4), 0 0 24px rgb(200 16 46 / 0.65);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
background: transparent;
|
||||
border: 1px solid var(--hg-crimson);
|
||||
color: var(--hg-crimson);
|
||||
|
||||
&:hover {
|
||||
background: var(--hg-crimson);
|
||||
color: #fff4ee;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--hg-panel);
|
||||
border: 1px solid var(--hg-border);
|
||||
color: var(--hg-text);
|
||||
}
|
||||
|
||||
.spin-source {
|
||||
background: #eadfd2;
|
||||
|
||||
> .btn-primary {
|
||||
background: linear-gradient(180deg, #c01733, var(--hg-crimson-deep));
|
||||
}
|
||||
|
||||
> .btn-outline-primary {
|
||||
background: transparent;
|
||||
color: var(--hg-crimson-deep);
|
||||
|
||||
&:hover {
|
||||
background: var(--hg-panel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spin-result {
|
||||
font-weight: 700;
|
||||
|
||||
strong {
|
||||
color: var(--hg-crimson);
|
||||
}
|
||||
}
|
||||
|
||||
.spin-options {
|
||||
background: var(--hg-panel);
|
||||
border: 1px solid var(--hg-border);
|
||||
border-top: 3px solid var(--hg-crimson);
|
||||
border-radius: 3px;
|
||||
|
||||
li:hover {
|
||||
background: #f8e6e2;
|
||||
}
|
||||
}
|
||||
|
||||
.spin-options-header {
|
||||
border-bottom: 1px solid var(--hg-border);
|
||||
color: var(--hg-crimson-deep);
|
||||
}
|
||||
|
||||
.spin-empty {
|
||||
border-color: var(--hg-border);
|
||||
}
|
||||
|
||||
.spin-wheel-pointer {
|
||||
border-top-color: #c8102e;
|
||||
filter: drop-shadow(0 0 4px rgb(200 16 46 / 0.7));
|
||||
}
|
||||
|
||||
.spin-wheel-rim {
|
||||
filter: drop-shadow(0 0 6px rgb(200 16 46 / 0.6));
|
||||
}
|
||||
|
||||
.spin-seg {
|
||||
stroke: #12070a;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.spin-seg-label {
|
||||
font-family: var(--hg-font);
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
// Tmavý režim — půlnoc Pekelné korespondence
|
||||
[data-bs-theme="dark"] .spin-wheel-modal.spin-theme-hellgirl {
|
||||
--hg-crimson: #e0334f;
|
||||
--hg-crimson-deep: #b81c38;
|
||||
--hg-panel: #170c0f;
|
||||
--hg-border: #3d1c22;
|
||||
--hg-text: #f3e6e2;
|
||||
--wheel-c1: #2e1a1e;
|
||||
|
||||
.modal-content {
|
||||
background:
|
||||
radial-gradient(ellipse 120% 45% at 50% 115%, rgb(140 12 36 / 0.35), transparent 70%),
|
||||
linear-gradient(180deg, #1c0a12, #0c0608 260px);
|
||||
}
|
||||
|
||||
.spin-steps span:not(.active):not(.done) {
|
||||
background: #221216;
|
||||
color: #8a6c6a;
|
||||
}
|
||||
|
||||
.btn-outline-primary, .btn-secondary {
|
||||
background: #170c0f;
|
||||
color: #f3e6e2;
|
||||
}
|
||||
|
||||
.spin-source {
|
||||
background: #221216;
|
||||
|
||||
> .btn-outline-primary {
|
||||
background: transparent;
|
||||
color: #c9aeaa;
|
||||
|
||||
&:hover {
|
||||
background: #170c0f;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spin-options li:hover {
|
||||
background: #2a1218;
|
||||
}
|
||||
}
|
||||
|
||||
// Ikona kola se při najetí pootočí
|
||||
.spin-wheel-icon {
|
||||
transition: transform 0.4s ease !important;
|
||||
|
||||
@@ -11,12 +11,13 @@ const STYLE_THEME_KEY = 'style_theme';
|
||||
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';
|
||||
export type StyleTheme = 'simple' | 'umamusume' | 'mlp' | 'hellgirl';
|
||||
|
||||
export const STYLE_THEMES: { value: StyleTheme, label: string }[] = [
|
||||
{ value: 'simple', label: 'Jednoduchý' },
|
||||
{ value: 'umamusume', label: 'Umamusume' },
|
||||
{ value: 'mlp', label: 'My Little Pony' },
|
||||
{ value: 'hellgirl', label: 'Hell Girl' },
|
||||
];
|
||||
|
||||
export type SettingsContextProps = {
|
||||
@@ -216,7 +217,7 @@ function useProvideSettings(): SettingsContextProps {
|
||||
}, [styleTheme]);
|
||||
|
||||
// Aplikuje accent barvy při změně hue nebo přepnutí světlý/tmavý.
|
||||
// Tematické styly (Umamusume, My Little Pony) mají vlastní pevnou paletu, accent barvu proto uplatňuje jen jednoduchý styl.
|
||||
// Tematické styly (Umamusume, My Little Pony, Hell Girl) mají vlastní pevnou paletu, accent barvu proto uplatňuje jen jednoduchý styl.
|
||||
useEffect(() => {
|
||||
localStorage.setItem(ACCENT_HUE_KEY, String(accentHue));
|
||||
if (styleTheme === 'simple') {
|
||||
|
||||
@@ -0,0 +1,425 @@
|
||||
// ============================================
|
||||
// STYL "HELL GIRL" (volitelný, výchozí je jednoduchý styl)
|
||||
// Inspirováno anime Jigoku Shōjo (地獄少女).
|
||||
// Věčný soumrak Ai Enmy (purpurovo-rudé nebe), papír washi, karmínová rudá
|
||||
// nit ze slaměné panenky, květy lilie pavoukovité (higanbana) a ostré,
|
||||
// tradičně japonské tvary s písmem mincho.
|
||||
// Tmavý režim = půlnoc, kdy se otevírá Pekelná korespondence (地獄通信).
|
||||
// Aktivní, pokud má <html> atribut data-luncher-theme="hellgirl".
|
||||
// ============================================
|
||||
|
||||
$hg: 'html[data-luncher-theme="hellgirl"]';
|
||||
|
||||
// Nebe věčného soumraku (fialová → karmínová → oranžová)
|
||||
$hg-twilight: linear-gradient(90deg, #1e0a1c, #4a0f2a 35%, #8e0f24 70%, #c8452a);
|
||||
// Rudá nit — tenká linka s jemnou září
|
||||
$hg-thread: linear-gradient(90deg, transparent, #c8102e 12%, #e8344f 50%, #c8102e 88%, transparent);
|
||||
|
||||
#{$hg} {
|
||||
--hg-font: 'Shippori Mincho', 'Noto Serif JP', 'Georgia', serif;
|
||||
--hg-crimson: #a3122a; // kimono Ai / rudá nit
|
||||
--hg-crimson-deep: #7a0c1f;
|
||||
--hg-crimson-shadow: #4a0612;
|
||||
--hg-higanbana: #d4203c; // lilie pavoukovitá
|
||||
--hg-dusk: #c8452a; // oranžové světlo soumraku
|
||||
--hg-ink: #1c1012; // černé vlasy / tuš
|
||||
--hg-washi: #f4ece0; // papír washi
|
||||
--hg-card-border: #dccbbb;
|
||||
--hg-petal: rgb(163 18 42 / 0.10);
|
||||
--hg-petal-2: rgb(200 69 42 / 0.08);
|
||||
|
||||
--luncher-primary: var(--hg-crimson);
|
||||
--luncher-primary-hover: var(--hg-crimson-deep);
|
||||
--luncher-primary-light: #f7e3e2;
|
||||
--luncher-bg: var(--hg-washi);
|
||||
--luncher-bg-card: #fffbf5;
|
||||
--luncher-bg-hover: #f8ebe6;
|
||||
--luncher-text: var(--hg-ink);
|
||||
--luncher-text-secondary: #4f3a3c;
|
||||
--luncher-text-muted: #8c7672;
|
||||
--luncher-border: var(--hg-card-border);
|
||||
--luncher-border-light: #eadfd2;
|
||||
--luncher-navbar-bg: var(--hg-crimson-deep);
|
||||
--luncher-navbar-text: #fff4ee;
|
||||
--luncher-action-icon: var(--hg-crimson);
|
||||
--luncher-buyer-icon: var(--hg-dusk);
|
||||
--luncher-success: #3d7a4a;
|
||||
--luncher-warning: #c07a1a;
|
||||
--luncher-danger: #c8102e;
|
||||
// Ostré, tradiční tvary — téměř bez zaoblení
|
||||
--luncher-radius-sm: 2px;
|
||||
--luncher-radius: 3px;
|
||||
--luncher-radius-lg: 4px;
|
||||
--luncher-radius-xl: 6px;
|
||||
--luncher-shadow: 0 3px 12px -4px rgb(74 6 18 / 0.25);
|
||||
--luncher-shadow-lg: 0 10px 26px -8px rgb(74 6 18 / 0.38);
|
||||
|
||||
&[data-bs-theme="dark"] {
|
||||
// Půlnoc — Pekelná korespondence je otevřena
|
||||
--hg-crimson: #e0334f;
|
||||
--hg-crimson-deep: #b81c38;
|
||||
--hg-crimson-shadow: #3a0510;
|
||||
--hg-higanbana: #ff4d63;
|
||||
--hg-dusk: #ff7a4a;
|
||||
--hg-card-border: #3d1c22;
|
||||
--hg-petal: rgb(224 51 79 / 0.22);
|
||||
--hg-petal-2: rgb(255 122 74 / 0.12);
|
||||
|
||||
--luncher-primary: #ec4a63;
|
||||
--luncher-primary-hover: #ff6b80;
|
||||
--luncher-primary-light: #2e1015;
|
||||
--luncher-bg: #0c0608;
|
||||
--luncher-bg-card: #170c0f;
|
||||
--luncher-bg-hover: #221216;
|
||||
--luncher-text: #f3e6e2;
|
||||
--luncher-text-secondary: #c9aeaa;
|
||||
--luncher-text-muted: #8a6c6a;
|
||||
--luncher-border: #3d1c22;
|
||||
--luncher-border-light: #2a1418;
|
||||
--luncher-navbar-bg: #12070a;
|
||||
--luncher-action-icon: #ec4a63;
|
||||
--luncher-buyer-icon: #ff9a6b;
|
||||
--luncher-success: #6fbf7e;
|
||||
--luncher-warning: #f0b45a;
|
||||
--luncher-danger: #ff4d63;
|
||||
--luncher-shadow: 0 4px 16px -4px rgb(0 0 0 / 0.7);
|
||||
--luncher-shadow-lg: 0 12px 30px -8px rgb(0 0 0 / 0.8), 0 0 18px -6px rgb(224 51 79 / 0.25);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--hg-font);
|
||||
background-color: var(--luncher-bg);
|
||||
// Rozfoukané okvětní lístky higanbany nad papírem, nahoře záře soumraku
|
||||
background-image:
|
||||
radial-gradient(ellipse 3px 1.5px at 20% 30%, var(--hg-petal) 0 100%, transparent 101%),
|
||||
radial-gradient(ellipse 2px 4px at 70% 75%, var(--hg-petal-2) 0 100%, transparent 101%),
|
||||
radial-gradient(ellipse 2.5px 1.2px at 85% 15%, var(--hg-petal) 0 100%, transparent 101%),
|
||||
linear-gradient(180deg, #f3d9cc 0, var(--luncher-bg) 380px);
|
||||
background-size: 150px 150px, 210px 210px, 260px 260px, 100% 100%;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] body {
|
||||
// Půlnoční tma s rudou září pekelného ohně odspodu
|
||||
background-image:
|
||||
radial-gradient(ellipse 3px 1.5px at 20% 30%, var(--hg-petal) 0 100%, transparent 101%),
|
||||
radial-gradient(ellipse 2px 4px at 70% 75%, var(--hg-petal-2) 0 100%, transparent 101%),
|
||||
radial-gradient(ellipse 2.5px 1.2px at 85% 15%, var(--hg-petal) 0 100%, transparent 101%),
|
||||
radial-gradient(ellipse 120% 45% at 50% 115%, rgb(140 12 36 / 0.35), transparent 70%),
|
||||
linear-gradient(180deg, #1c0a12 0, var(--luncher-bg) 420px);
|
||||
background-size: 150px 150px, 210px 210px, 260px 260px, 100% 100%, 100% 100%;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, .navbar-brand, .btn {
|
||||
font-family: var(--hg-font);
|
||||
}
|
||||
|
||||
// ---------- Navbar: nebe soumraku s rudou nití dole ----------
|
||||
.navbar {
|
||||
background:
|
||||
#{$hg-thread} bottom / 100% 2px no-repeat,
|
||||
#{$hg-twilight} !important;
|
||||
padding-bottom: calc(var(--bs-navbar-padding-y, 0.5rem) + 2px);
|
||||
box-shadow: 0 3px 14px -2px rgb(74 6 18 / 0.5);
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.5px;
|
||||
text-shadow: 0 0 12px rgb(232 52 79 / 0.6);
|
||||
|
||||
// Pekelná korespondence — otevírá se jen o půlnoci
|
||||
&::after {
|
||||
content: '地獄通信';
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
font-size: 0.55em;
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
vertical-align: middle;
|
||||
color: #ffb3a8;
|
||||
opacity: 0.85;
|
||||
animation: hg-flicker 5s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
border: 1px solid var(--hg-crimson);
|
||||
border-radius: var(--luncher-radius);
|
||||
}
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] .navbar {
|
||||
background:
|
||||
#{$hg-thread} bottom / 100% 2px no-repeat,
|
||||
linear-gradient(90deg, #0a0406, #1f0810 55%, #3d0a18) !important;
|
||||
}
|
||||
|
||||
// ---------- Titulek dne: černá lakovaná deska s rudým lemem ----------
|
||||
.day-navigator .title {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 6px 28px 8px;
|
||||
font-weight: 800;
|
||||
font-size: 1.7rem;
|
||||
letter-spacing: 1px;
|
||||
color: #fff4ee !important;
|
||||
background: linear-gradient(180deg, #2a1216, #12070a);
|
||||
border: 1px solid var(--hg-crimson);
|
||||
outline: 1px solid rgb(163 18 42 / 0.35);
|
||||
outline-offset: 3px;
|
||||
border-radius: var(--luncher-radius-sm);
|
||||
text-shadow: 0 0 10px rgb(232 52 79 / 0.55);
|
||||
box-shadow: var(--luncher-shadow);
|
||||
|
||||
// Květy higanbany po stranách
|
||||
&::before, &::after {
|
||||
content: '✿';
|
||||
font-size: 0.6em;
|
||||
margin: 0 10px;
|
||||
vertical-align: middle;
|
||||
color: var(--hg-higanbana);
|
||||
text-shadow: 0 0 6px rgb(212 32 60 / 0.7);
|
||||
}
|
||||
|
||||
&.text-muted {
|
||||
background: linear-gradient(180deg, #6b5a5a, #4a3d3e);
|
||||
border-color: #8c7672;
|
||||
outline-color: rgb(140 118 114 / 0.35);
|
||||
text-shadow: none;
|
||||
|
||||
&::before, &::after {
|
||||
color: #b3a19d;
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Karty podniků ----------
|
||||
.restaurant-card {
|
||||
border: 1px solid var(--hg-card-border);
|
||||
border-top: 3px solid var(--hg-crimson);
|
||||
border-radius: var(--luncher-radius-lg);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--hg-crimson);
|
||||
box-shadow: var(--luncher-shadow-lg), 0 0 0 1px rgb(200 16 46 / 0.25);
|
||||
}
|
||||
|
||||
.restaurant-header {
|
||||
position: relative;
|
||||
background:
|
||||
#{$hg-thread} bottom / 100% 2px no-repeat,
|
||||
linear-gradient(90deg, #1c0a10, var(--hg-crimson-deep));
|
||||
|
||||
&:hover {
|
||||
background:
|
||||
#{$hg-thread} bottom / 100% 2px no-repeat,
|
||||
#{$hg-twilight};
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.5px;
|
||||
text-shadow: 0 1px 0 rgb(0 0 0 / 0.4);
|
||||
}
|
||||
|
||||
small {
|
||||
color: rgb(255 244 238 / 0.85);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.food-table tbody tr:hover > td {
|
||||
background: #f8e6e2;
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] .food-table tbody tr:hover > td {
|
||||
background: #2a1218;
|
||||
}
|
||||
|
||||
.food-price {
|
||||
color: var(--hg-crimson);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
// ---------- Panely a tabulka voleb ----------
|
||||
.choice-section,
|
||||
.choices-table,
|
||||
.pizza-section,
|
||||
.no-votes {
|
||||
border: 1px solid var(--hg-card-border);
|
||||
border-radius: var(--luncher-radius-lg);
|
||||
box-shadow: var(--luncher-shadow);
|
||||
}
|
||||
|
||||
// Sloupec s podnikem: černá tuš s rudou nití na okraji
|
||||
.choices-table > tbody > tr > td:first-child {
|
||||
background: linear-gradient(90deg, #1c1012, #2e1418);
|
||||
color: #fff4ee;
|
||||
font-weight: 700;
|
||||
border-right: 2px solid var(--hg-crimson);
|
||||
}
|
||||
|
||||
&[data-bs-theme="dark"] .choices-table > tbody > tr > td:first-child {
|
||||
background: linear-gradient(90deg, #22090f, #170c0f);
|
||||
color: var(--hg-higanbana);
|
||||
}
|
||||
|
||||
// Bootstrap karty (skupinové objednávky apod.) a seznamy
|
||||
.card {
|
||||
border: 1px solid var(--hg-card-border);
|
||||
border-radius: var(--luncher-radius-lg);
|
||||
box-shadow: var(--luncher-shadow);
|
||||
background: var(--luncher-bg-card);
|
||||
|
||||
> .card-header {
|
||||
background: var(--luncher-primary-light);
|
||||
border-bottom: 2px solid var(--hg-crimson);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.table {
|
||||
--bs-table-bg: transparent;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.list-group {
|
||||
border-radius: var(--luncher-radius);
|
||||
border: 1px solid var(--hg-card-border);
|
||||
}
|
||||
|
||||
.form-select, .form-control,
|
||||
input[type="text"], input[type="email"], input[type="password"] {
|
||||
border-radius: var(--luncher-radius-sm);
|
||||
|
||||
&:focus {
|
||||
border-color: var(--hg-crimson);
|
||||
box-shadow: 0 0 0 3px rgb(200 16 46 / 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Tlačítka: ostré, karmínová s dvojitou linkou jako pás obi ----------
|
||||
.btn {
|
||||
border-radius: var(--luncher-radius-sm);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.4px;
|
||||
transition: transform 0.12s ease, box-shadow 0.2s ease, background 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(180deg, #c01733, var(--hg-crimson-deep));
|
||||
border: 1px solid var(--hg-crimson-shadow);
|
||||
color: #fff4ee;
|
||||
box-shadow: inset 0 0 0 2px rgb(255 255 255 / 0.12);
|
||||
|
||||
// Rudá záře pekelného ohně při najetí
|
||||
&:hover {
|
||||
background: linear-gradient(180deg, #d41d3b, #8e0f24);
|
||||
box-shadow: inset 0 0 0 2px rgb(255 255 255 / 0.18), 0 0 14px rgb(212 32 60 / 0.5);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #b9a3a0;
|
||||
border-color: #9c8582;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
background: transparent;
|
||||
border: 1px solid var(--hg-crimson);
|
||||
color: var(--luncher-primary);
|
||||
|
||||
&:hover {
|
||||
background: var(--hg-crimson);
|
||||
color: #fff4ee;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--luncher-bg-card);
|
||||
border: 1px solid var(--luncher-border);
|
||||
color: var(--luncher-text);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--hg-crimson);
|
||||
background: var(--luncher-bg-hover);
|
||||
color: var(--luncher-text);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: linear-gradient(180deg, #e0243f, #8e0f24);
|
||||
border: 1px solid var(--hg-crimson-shadow);
|
||||
}
|
||||
|
||||
.action-icon:hover {
|
||||
background: var(--luncher-primary-light);
|
||||
color: var(--hg-higanbana);
|
||||
}
|
||||
|
||||
// ---------- Modaly ----------
|
||||
.modal-content {
|
||||
border: 1px solid var(--hg-crimson);
|
||||
border-radius: var(--luncher-radius-xl);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background:
|
||||
#{$hg-thread} bottom / 100% 2px no-repeat,
|
||||
#{$hg-twilight};
|
||||
border-bottom: none;
|
||||
|
||||
.modal-title, .modal-title h2 {
|
||||
color: #fff4ee;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.5px;
|
||||
text-shadow: 0 0 10px rgb(232 52 79 / 0.5);
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
filter: invert(1) grayscale(1) brightness(2);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: none;
|
||||
background-image: #{$hg-thread};
|
||||
background-size: 100% 1px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: top;
|
||||
}
|
||||
}
|
||||
|
||||
// Mihotání nápisu jako plamen svíčky
|
||||
@keyframes hg-flicker {
|
||||
0%, 100% { opacity: 0.85; }
|
||||
45% { opacity: 0.85; }
|
||||
48% { opacity: 0.35; }
|
||||
50% { opacity: 0.9; }
|
||||
53% { opacity: 0.5; }
|
||||
56% { opacity: 0.85; }
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
#{$hg} .navbar .navbar-brand::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#{$hg} .navbar .navbar-brand::after {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user