feat: nový vzhled Aggretsuko
CI / Generate TypeScript types (push) Successful in 11s
CI / Server unit tests (push) Successful in 27s
CI / Build server (push) Successful in 32s
CI / Build client (push) Successful in 42s
CI / Playwright E2E tests (push) Successful in 1m55s
CI / Build and push Docker image (push) Successful in 45s
CI / Notify (push) Successful in 2s

This commit is contained in:
mates committed 2026-10-25 21:04:44 +01:00
1 parent 6fde8f3ec1
commit 46d8dac45b
10 files changed
+1054 -6

No files matched your search

+2 -2
View File
@@ -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&family=Shippori+Mincho:wght@600;700;800&family=Zen+Maru+Gothic:wght@500;700;900&family=Yusei+Magic&family=Dela+Gothic+One&family=Barlow+Condensed:wght@500;600;700&family=Kiwi+Maru:wght@400;500&family=Playfair+Display:ital,wght@0,800;1,800&family=Courier+Prime:wght@700&family=Cormorant+Garamond:wght@600;700&family=Baloo+2:wght@600;800&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&family=Zen+Maru+Gothic:wght@500;700;900&family=Yusei+Magic&family=Dela+Gothic+One&family=Barlow+Condensed:wght@500;600;700&family=Kiwi+Maru:wght@400;500&family=Playfair+Display:ital,wght@0,800;1,800&family=Courier+Prime:wght@700&family=Cormorant+Garamond:wght@600;700&family=Baloo+2:wght@600;800&family=Metal+Mania&display=swap" rel="stylesheet" />
<title>Luncher</title>
<script>
(function() {
@@ -28,7 +28,7 @@
}
document.documentElement.setAttribute('data-bs-theme', theme);
var style = localStorage.getItem('style_theme');
var styles = ['umamusume', 'mlp', 'hellgirl', 'kon', 'bocchi', 'evangelion', 'yurucamp', 'spyfamily', 'apothecary', 'frieren', 'mlpg5'];
var styles = ['umamusume', 'mlp', 'hellgirl', 'kon', 'bocchi', 'evangelion', 'yurucamp', 'spyfamily', 'apothecary', 'frieren', 'mlpg5', 'aggretsuko'];
document.documentElement.setAttribute('data-luncher-theme', styles.indexOf(style) >= 0 ? style : 'simple');
} catch (e) {
// Fallback pokud localStorage není dostupný
+1
View File
@@ -21,6 +21,7 @@ import './themes/spyfamily.scss';
import './themes/apothecary.scss';
import './themes/frieren.scss';
import './themes/mlpg5.scss';
import './themes/aggretsuko.scss';
import { faCircleCheck, faNoteSticky, faTrashCan, faComment } from '@fortawesome/free-regular-svg-icons';
import { useSettings } from './context/settings';
import Footer from './components/Footer';
+11
View File
@@ -140,6 +140,17 @@
box-shadow: 0 2px 6px rgb(238 95 168 / 0.45);
}
// Styl Aggretsuko: emoji v kruhovém razítku hanko
.eater-badge-aggretsuko {
display: inline-grid;
place-items: center;
font-size: 15px;
line-height: 1;
border-color: #c8102e;
background: radial-gradient(circle at 40% 35%, #fff, #fff4e8);
box-shadow: 0 0 0 1px #fff, 0 0 0 3px #c8102e;
}
// Odznak a jméno drží na jednom řádku (úzký sloupec „Člen" na stránce objednávek)
.user-info:has(.eater-badge) {
display: inline-flex;
+8 -1
View File
@@ -73,6 +73,12 @@ const G5_LEVELS = [
{ minItems: 2, emoji: '🥤', title: 'Úroveň Sunny — k obědu ještě smoothie z jejího stánku' },
];
/** Úrovně ve stylu Aggretsuko — emoji v kruhovém razítku hanko. Seřazeno od nejvyšší úrovně. */
const AGG_LEVELS = [
{ minItems: 3, emoji: '🐷', title: 'Úroveň šéfa Tona — sní všechno a ještě si přidá, šéf má přece vždycky pravdu' },
{ minItems: 2, emoji: '🍙', title: 'Úroveň Retsuko — po ránu se šéfem k obědu ještě onigiri na uklidnění' },
];
/** Úrovně podle stylu pro odznaky s emoji. */
const EMOJI_LEVELS: Partial<Record<StyleTheme, typeof MLP_LEVELS>> = {
mlp: MLP_LEVELS,
@@ -85,6 +91,7 @@ const EMOJI_LEVELS: Partial<Record<StyleTheme, typeof MLP_LEVELS>> = {
apothecary: APO_LEVELS,
frieren: FR_LEVELS,
mlpg5: G5_LEVELS,
aggretsuko: AGG_LEVELS,
};
type Props = {
@@ -92,7 +99,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 a My Little Pony G5. */
/** 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 a Aggretsuko. */
export default function EaterBadge({ items }: Readonly<Props>) {
const styleTheme = useSettings()?.styleTheme;
const isUma = styleTheme === 'umamusume';
+82
View File
@@ -958,3 +958,85 @@
}
}
}
// ============================================
// Konfety ve stylu Aggretsuko
// ============================================
.confetti-agg {
filter: drop-shadow(0 3px 3px rgb(0 0 0 / 0.25));
}
// Štítky jako kancelářské cedulky — a některé už v rage mode
.confetti-agg-badge {
padding: 4px 14px;
border-radius: 4px;
border: 2px solid rgb(255 255 255 / 0.85);
color: #fff;
font-family: 'Metal Mania', 'M PLUS Rounded 1c', sans-serif;
font-size: 1.3rem;
letter-spacing: 0.03em;
white-space: nowrap;
}
// Nápis „RAAAAAH!!!" — Retsuko řve do mikrofonu, rudý text na černé se zubatými okraji
.confetti-agg-banner {
position: absolute;
top: 38%;
left: 50%;
transform: translate(-50%, -50%);
padding: 18px 46px 20px;
font-family: 'Metal Mania', 'M PLUS Rounded 1c', sans-serif;
font-size: clamp(2rem, 8vw, 4.6rem);
letter-spacing: 0.04em;
white-space: nowrap;
color: #ff2e3f;
text-shadow: 0 0 18px rgb(255 46 63 / 0.9), 3px 3px 0 #000;
background:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='8'%3E%3Cpath d='M0 7L4 1L8 7L12 1L16 7' fill='none' stroke='%23ff2e3f' stroke-width='2'/%3E%3C/svg%3E") left top / 16px 8px repeat-x,
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='8'%3E%3Cpath d='M0 7L4 1L8 7L12 1L16 7' fill='none' stroke='%23ff2e3f' stroke-width='2'/%3E%3C/svg%3E") left bottom / 16px 8px repeat-x,
#000;
box-shadow: 0 0 40px rgb(255 46 63 / 0.5);
animation: agg-banner 2.6s ease both;
}
@keyframes agg-banner {
0% { transform: translate(-50%, -50%) scale(3); opacity: 0; }
10% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
13% { transform: translate(calc(-50% - 8px), calc(-50% + 4px)) rotate(-2deg); }
16% { transform: translate(calc(-50% + 8px), calc(-50% - 4px)) rotate(2deg); }
19% { transform: translate(calc(-50% - 5px), -50%) rotate(-1deg); }
22% { transform: translate(calc(-50% + 5px), -50%); }
26%, 80% { transform: translate(-50%, -50%); opacity: 1; }
100% { transform: translate(-50%, -50%) scale(0.9); opacity: 0; }
}
// Postava s hláškou — razítko hanko a bublina jako kancelářský lepicí papírek
.confetti-aggretsuko-voice {
.confetti-mlp-avatar {
border: 3px solid #fff;
background: radial-gradient(circle at 40% 35%, #fff, #fff4e8);
box-shadow: 0 0 0 3px var(--mlp-speaker, #c8102e), 0 4px 10px rgb(0 0 0 / 0.3);
}
.confetti-mlp-bubble {
background: linear-gradient(170deg, #fff08f, #ffe873);
color: #3a3220;
border: none;
border-left: 5px solid var(--mlp-speaker, #d9673a);
border-radius: 2px 2px 14px 2px;
box-shadow: 2px 6px 12px rgb(0 0 0 / 0.25);
font-family: 'M PLUS Rounded 1c', 'Nunito', sans-serif;
p {
font-weight: 800;
}
small {
color: #6b5a2a;
}
&::before {
border-right-color: #ffe873;
}
}
}
+17 -1
View File
@@ -166,6 +166,21 @@ const G5_SPEAKERS = [
{ name: 'Sparky Sparkeroni', emoji: '🐉', color: '#7cb342', quote: '(škytne a vypustí obláček duhových jisker)' },
{ name: 'Královna Haven', emoji: '👑', color: '#d4a21e', quote: 'Královská volba, mé drahé. Zephyr Heights by souhlasily.' },
];
const AGG_EMOJI = ['🎤', '🤘', '🔥', '💢', '☕', '📎', '📊', '🗂️', '🍺', '🍙', '📱', '🐷', '🎸', '💼'];
// Hlášky z Aggretsuko (karaoke, přesčasy, účetnictví, šéf Ton, pátek...)
const AGG_BADGES = ['DEATH METAL!', 'Přesčas', 'Účetnictví', 'Karaoke!', 'Šéf Ton…', 'Excel', 'Pátek!!!', 'Rage mode', '承認', 'Ještě jedna tabulka'];
const AGG_BADGE_COLORS = ['#d9673a', '#c8102e', '#4a5d7a', '#1a1414', '#e8a0b8', '#c9a227'];
/** Retsuko a kolegové z Carrier Man Trading s „hláškou" po finálním výsledku. */
const AGG_SPEAKERS = [
{ name: 'Retsuko', emoji: '🎤', color: '#d9673a', quote: 'Ano, hned to bude… (v duchu: KONEČNĚ OBĚD, VY PROKLETÉ TABULKY!!!)' },
{ name: 'Fenneko', emoji: '📱', color: '#c9a27a', quote: 'Hmm. O tom podniku už jsem na sítích našla úplně všechno. Hehehe.' },
{ name: 'Haida', emoji: '🎸', color: '#6b6f7a', quote: 'R-Retsuko, můžu jít s tebou? Teda… jestli chceš.' },
{ name: 'Šéf Ton', emoji: '🐷', color: '#d48a9a', quote: 'Hej, holka! Dones mi taky jeden, a ať je to rychle!' },
{ name: 'Gori', emoji: '🦍', color: '#8a6aa8', quote: 'Retsuko, to je tak inspirativní volba! Jsem na tebe pyšná!' },
{ name: 'Washimi', emoji: '🦅', color: '#4a5d7a', quote: 'Efektivní rozhodnutí. Oceňuji, že na tom nestrávila celou poradu.' },
{ name: 'Tsunoda', emoji: '🦌', color: '#e8a0b8', quote: 'Jééé, to je tak roztomilý podnik! Šéfe, pojďte taky~' },
{ name: 'Kabae', emoji: '🦛', color: '#9ab0c8', quote: 'Prý tam vaří skvěle — a taky jsem o nich slyšela pár drbů…' },
];
/** 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[],
@@ -182,6 +197,7 @@ const EMOJI_CONFETTI: Partial<Record<WheelTheme, {
apothecary: { prefix: 'confetti-apo', badges: APO_BADGES, colors: APO_BADGE_COLORS, emoji: APO_EMOJI, speakers: APO_SPEAKERS, banner: '🏮 薬屋のひとりごと 🏮' },
frieren: { prefix: 'confetti-fr', badges: FR_BADGES, colors: FR_BADGE_COLORS, emoji: FR_EMOJI, speakers: FR_SPEAKERS, banner: '✦ 葬送のフリーレン ✦' },
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!!! 🎤' },
};
const DURATION_MS = 6400;
@@ -335,7 +351,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...)
// 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...)
const emojiConfetti = EMOJI_CONFETTI[theme];
const emojiSpeaker = useMemo(
() => big && emojiConfetti ? pick(emojiConfetti.speakers) : undefined,
@@ -3105,6 +3105,315 @@
}
}
// ============================================
// STYL AGGRETSUKO kola štěstí (dle stylu zvoleného v nastavení).
// Výseče v barvách kanceláře (rezavá Retsuko, ocelově modrá, lepicí
// papírek, razítko, růžová Tsunoda, šedá Haida), střed jako razítko hanko.
// V rage mode černé kolo s rudými výsečemi.
// ============================================
.spin-wheel-modal.spin-theme-aggretsuko {
--agg-font: 'Nunito', sans-serif;
--agg-display: 'M PLUS Rounded 1c', 'Nunito', sans-serif;
--agg-metal: 'Metal Mania', 'M PLUS Rounded 1c', sans-serif;
--agg-retsuko: #d9673a;
--agg-retsuko-deep: #b4481f;
--agg-office: #4a5d7a;
--agg-line: #ecdccb;
--agg-panel: #ffffff;
--agg-text: #2f2a2a;
--wheel-c0: #d9673a;
--wheel-c1: #4a5d7a;
--wheel-c2: #e8b923;
--wheel-c3: #c8102e;
--wheel-c4: #e08aa6;
--wheel-c5: #6b6f7a;
--wheel-rim: #394a63;
--wheel-hub: #fff4e8;
font-family: var(--agg-font);
.modal-content {
border: 1px solid var(--agg-line);
border-radius: 16px;
overflow: hidden;
background:
linear-gradient(rgb(74 93 122 / 0.07) 1px, transparent 1px) 0 0 / 100% 26px,
linear-gradient(90deg, rgb(74 93 122 / 0.07) 1px, transparent 1px) 0 0 / 104px 100%,
#fbf6ef;
color: var(--agg-text);
}
.modal-header {
background:
linear-gradient(var(--agg-retsuko), var(--agg-retsuko)) left bottom / 100% 4px no-repeat,
var(--agg-office);
border-bottom: none;
h2 {
color: #fff;
font-family: var(--agg-display);
font-weight: 800;
}
.btn-close {
filter: invert(1) grayscale(1) brightness(2);
opacity: 0.9;
}
}
.modal-footer {
border-top: 1px solid var(--agg-line);
}
.spin-steps span {
font-family: var(--agg-display);
font-weight: 800;
background: #f1e8de;
color: #8f8584;
border-radius: 6px;
padding: 4px 16px;
&.active {
background: var(--agg-retsuko);
color: #fff;
}
&.done {
background: #ffe873;
color: #3a3220;
}
}
.btn {
font-family: var(--agg-font);
font-weight: 800;
border-radius: 6px;
}
.btn-primary {
background: var(--agg-retsuko);
border: 1px solid var(--agg-retsuko-deep);
color: #fff;
box-shadow: 0 2px 0 var(--agg-retsuko-deep);
&:hover:not(:disabled) {
background: var(--agg-retsuko-deep);
color: #fff;
}
&:disabled {
background: #e4dcd4;
border-color: #e4dcd4;
color: #8f8584;
box-shadow: none;
}
}
// Tlačítko roztočení — Retsuko se chystá zařvat
.spin-button {
font-family: var(--agg-display);
font-size: 1.2rem;
padding: 10px 38px;
&:hover:not(:disabled) {
background: #c8102e;
border-color: #8f0a20;
animation: agg-rage 0.35s ease-in-out;
}
}
.btn-outline-primary {
background: var(--agg-panel);
border: 1px solid var(--agg-retsuko);
color: var(--agg-retsuko-deep);
&:hover {
background: #fde6d8;
color: var(--agg-retsuko-deep);
}
}
.btn-secondary {
background: var(--agg-office);
border: 1px solid #394a63;
color: #fff;
}
.spin-source {
background: #f1e8de;
border-radius: 8px;
> .btn {
border-radius: 6px !important;
}
> .btn-outline-primary {
background: transparent;
color: var(--agg-text);
&:hover {
background: var(--agg-panel);
}
}
}
.spin-result {
font-weight: 800;
strong {
color: var(--agg-retsuko-deep);
}
}
.spin-options {
background: var(--agg-panel);
border: 1px solid var(--agg-line);
border-radius: 12px;
li:hover {
background: #fff1e4;
}
}
.spin-options-header {
border-bottom: 2px solid var(--agg-retsuko);
color: var(--agg-office);
font-family: var(--agg-display);
}
.spin-empty {
border-color: var(--agg-line);
}
.spin-wheel-pointer {
border-top-color: #c8102e;
filter: drop-shadow(0 2px 1px rgb(0 0 0 / 0.3));
}
.spin-wheel-hub {
stroke: #c8102e;
}
.spin-seg {
stroke: #fff;
stroke-width: 2;
}
.spin-seg-label {
font-family: var(--agg-display);
font-weight: 800;
}
}
// Rage mode — karaoke box
[data-bs-theme="dark"] .spin-wheel-modal.spin-theme-aggretsuko {
--agg-retsuko: #ff4a3d;
--agg-retsuko-deep: #c8102e;
--agg-line: #3a1c20;
--agg-panel: #171314;
--agg-text: #f2ecea;
--wheel-c0: #c8102e;
--wheel-c1: #5c2a30;
--wheel-c2: #ff4a3d;
--wheel-c3: #3a1c20;
--wheel-c4: #8f0a20;
--wheel-c5: #6b4a4e;
--wheel-rim: #ff2e3f;
--wheel-hub: #000000;
.modal-content {
border-color: #ff2e3f;
background:
radial-gradient(ellipse 50% 40% at 50% -10%, rgb(255 46 63 / 0.3), transparent 70%),
#0d0b0c;
}
.modal-header {
background:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='8'%3E%3Cpath d='M0 7L4 1L8 7L12 1L16 7' fill='none' stroke='%23ff2e3f' stroke-width='2'/%3E%3C/svg%3E") left bottom / 16px 8px repeat-x,
#000;
padding-bottom: calc(var(--bs-modal-header-padding-y, 1rem) + 6px);
h2 {
font-family: var(--agg-metal);
font-weight: 400;
letter-spacing: 0.03em;
text-shadow: 0 0 10px rgb(255 46 63 / 0.8);
}
}
.spin-steps span:not(.active):not(.done) {
background: #221a1b;
color: #8f8584;
}
.spin-steps span.active {
background: #c8102e;
}
.spin-steps span.done {
background: #2a0f12;
color: #ff4a3d;
}
.btn-primary {
background: #c8102e;
border-color: #ff2e3f;
box-shadow: 0 0 12px rgb(255 46 63 / 0.45);
&:hover:not(:disabled) {
background: #e0283f;
}
&:disabled {
background: #2a1f20;
border-color: #2a1f20;
color: #8f8584;
box-shadow: none;
}
}
.spin-button {
font-family: var(--agg-metal);
font-weight: 400;
font-size: 1.4rem;
letter-spacing: 0.04em;
}
.btn-outline-primary:hover {
background: #3a1416;
}
.btn-secondary {
background: #241c1d;
border-color: #3a2a2c;
}
.spin-source {
background: #221a1b;
}
.spin-options li:hover {
background: #221a1b;
}
.spin-options-header {
color: #ff4a3d;
font-family: var(--agg-metal);
font-weight: 400;
letter-spacing: 0.03em;
}
.spin-wheel-hub {
stroke: #ff2e3f;
}
.spin-seg {
stroke: #ff2e3f;
stroke-width: 1.5;
}
}
// Ikona kola se při najetí pootočí
.spin-wheel-icon {
transition: transform 0.4s ease !important;
+3 -2
View File
@@ -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';
export type StyleTheme = 'simple' | 'umamusume' | 'mlp' | 'hellgirl' | 'kon' | 'bocchi' | 'evangelion' | 'yurucamp' | 'spyfamily' | 'apothecary' | 'frieren' | 'mlpg5' | 'aggretsuko';
export const STYLE_THEMES: { value: StyleTheme, label: string }[] = [
{ value: 'simple', label: 'Jednoduchý' },
@@ -28,6 +28,7 @@ export const STYLE_THEMES: { value: StyleTheme, label: string }[] = [
{ value: 'spyfamily', label: 'Spy × Family' },
{ value: 'apothecary', label: 'Apothecary Diaries' },
{ value: 'frieren', label: 'Frieren' },
{ value: 'aggretsuko', label: 'Aggretsuko' },
];
export type SettingsContextProps = {
@@ -259,7 +260,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) 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) mají vlastní pevnou paletu, accent barvu proto uplatňuje jen jednoduchý styl.
useEffect(() => {
localStorage.setItem(ACCENT_HUE_KEY, String(accentHue));
if (styleTheme === 'simple') {
+2
View File
@@ -27,6 +27,8 @@ const ICONS: Record<StyleTheme, Record<GameId, string>> = {
frieren: { drawing: '🪄' },
// Izzy Moonbow a její výtvory z vyřazených věcí
mlpg5: { drawing: '🦄' },
// Kancelářské pero z účetního oddělení Retsuko
aggretsuko: { drawing: '🖊️' },
};
/** Styl z <html data-luncher-theme> — pro místa mimo React kontext nastavení. */
+619
View File
@@ -0,0 +1,619 @@
// ============================================
// STYL "AGGRETSUKO" (volitelný, výchozí je jednoduchý styl)
// Inspirováno seriálem Aggretsuko (アグレッシブ烈子) od Sanria.
// Světlý režim = kancelář Carrier Man Trading: krémová a rezavá jako srst
// rudé pandy Retsuko, tabulková mřížka z účetnictví, žluté lepicí papírky
// a razítka 承認 (schváleno). Ocelově modrá kancelářská nuda.
// Tmavý režim = „rage mode" — Retsukino death-metalové karaoke po práci:
// černá, rudá, kovové písmo a zubaté hrany.
// Aktivní, pokud má <html> atribut data-luncher-theme="aggretsuko".
// ============================================
$agg: 'html[data-luncher-theme="aggretsuko"]';
// Zubatá linka (rage mode) — opakuje se vodorovně (barva jako parametr)
@function agg-zigzag($color) {
@return url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='8'%3E%3Cpath d='M0 7L4 1L8 7L12 1L16 7' fill='none' stroke='#{$color}' stroke-width='2'/%3E%3C/svg%3E");
}
#{$agg} {
--agg-font: 'Nunito', -apple-system, sans-serif;
--agg-display: 'M PLUS Rounded 1c', 'Nunito', sans-serif;
--agg-metal: 'Metal Mania', 'M PLUS Rounded 1c', sans-serif;
--agg-retsuko: #d9673a; // srst rudé pandy
--agg-retsuko-deep: #b4481f;
--agg-cream: #fff4e8;
--agg-office: #4a5d7a; // ocelově modrá kancelář
--agg-office-deep: #394a63;
--agg-postit: #ffe873; // lepicí papírek
--agg-hanko: #c8102e; // razítko / rage
--agg-grid: rgb(74 93 122 / 0.08);
--agg-line: #ecdccb;
--luncher-primary: var(--agg-retsuko-deep);
--luncher-primary-hover: #963a16;
--luncher-primary-light: #fde6d8;
--luncher-bg: #fbf6ef;
--luncher-bg-card: #ffffff;
--luncher-bg-hover: #fff1e4;
--luncher-text: #2f2a2a;
--luncher-text-secondary: #5a5150;
--luncher-text-muted: #8f8584;
--luncher-border: var(--agg-line);
--luncher-border-light: #f3e8dc;
--luncher-navbar-bg: var(--agg-office);
--luncher-navbar-text: #ffffff;
--luncher-action-icon: var(--agg-retsuko-deep);
--luncher-buyer-icon: var(--agg-office);
--luncher-success: #3f8f5a;
--luncher-warning: #d99a1e;
--luncher-danger: var(--agg-hanko);
--luncher-radius-sm: 6px;
--luncher-radius: 10px;
--luncher-radius-lg: 12px;
--luncher-radius-xl: 16px;
--luncher-shadow: 0 3px 10px rgb(74 93 122 / 0.10);
--luncher-shadow-lg: 0 10px 26px rgb(74 93 122 / 0.18);
&[data-bs-theme="dark"] {
// Rage mode — karaoke box, černá a rudá
--agg-retsuko: #ff4a3d;
--agg-retsuko-deep: #ff4a3d;
--agg-hanko: #ff2e3f;
--agg-line: #3a1c20;
--luncher-primary: #ff4a3d;
--luncher-primary-hover: #ff6f63;
--luncher-primary-light: #3a1416;
--luncher-bg: #0d0b0c;
--luncher-bg-card: #171314;
--luncher-bg-hover: #221a1b;
--luncher-text: #f2ecea;
--luncher-text-secondary: #c9bfbd;
--luncher-text-muted: #8f8584;
--luncher-border: var(--agg-line);
--luncher-border-light: #241a1b;
--luncher-navbar-bg: #000000;
--luncher-action-icon: #ff4a3d;
--luncher-buyer-icon: #ffb03b;
--luncher-success: #6fd99a;
--luncher-warning: #ffb03b;
--luncher-danger: #ff2e3f;
--luncher-shadow: 0 4px 16px rgb(0 0 0 / 0.5);
--luncher-shadow-lg: 0 0 0 1px #ff2e3f, 0 10px 30px rgb(255 46 63 / 0.25);
}
body {
font-family: var(--agg-font);
background-color: var(--luncher-bg);
// Tabulková mřížka jako v účetním Excelu, nahoře teplá krémová
background-image:
linear-gradient(var(--agg-grid) 1px, transparent 1px),
linear-gradient(90deg, var(--agg-grid) 1px, transparent 1px),
linear-gradient(180deg, var(--agg-cream) 0, var(--luncher-bg) 360px);
background-size: 100% 26px, 104px 100%, 100% 100%;
background-attachment: fixed;
}
&[data-bs-theme="dark"] body {
// Reflektor nad pódiem karaoke a škrábance jako na kytaře
background-image:
repeating-linear-gradient(-35deg, rgb(255 255 255 / 0.015) 0 1px, transparent 1px 9px),
radial-gradient(ellipse 45% 55% at 50% -12%, rgb(255 46 63 / 0.35), transparent 70%),
radial-gradient(ellipse 60% 40% at 50% 115%, rgb(255 46 63 / 0.12), transparent 70%);
background-size: auto, 100% 100%, 100% 100%;
}
.app-container {
background: transparent;
}
h1, h2, h3, h4, .navbar-brand {
font-family: var(--agg-display);
font-weight: 800;
}
// V rage mode všechny nadpisy přejdou do kovového písma
&[data-bs-theme="dark"] {
h1, h2, h3, h4, .navbar-brand {
font-family: var(--agg-metal);
font-weight: 400;
letter-spacing: 0.03em;
}
.navbar-brand {
text-shadow: 0 0 10px rgb(255 46 63 / 0.8);
}
}
.btn {
font-family: var(--agg-font);
}
// ---------- Navbar: ocelově modrá kancelář s rezavým proužkem ----------
.navbar {
background:
linear-gradient(var(--agg-retsuko), var(--agg-retsuko)) left bottom / 100% 4px no-repeat,
var(--luncher-navbar-bg) !important;
padding-bottom: calc(var(--bs-navbar-padding-y, 0.5rem) + 4px);
.navbar-brand {
// Karaoke mikrofon Retsuko
&::after {
content: '🎤';
display: inline-block;
margin-left: 6px;
margin-right: 0.6em;
font-size: 0.75em;
}
}
.dropdown-menu {
border: 1px solid var(--agg-line);
border-radius: var(--luncher-radius);
box-shadow: var(--luncher-shadow-lg);
}
}
&[data-bs-theme="dark"] .navbar {
background:
agg-zigzag('%23ff2e3f') left bottom / 16px 8px repeat-x,
var(--luncher-navbar-bg) !important;
padding-bottom: calc(var(--bs-navbar-padding-y, 0.5rem) + 8px);
.navbar-brand::after {
content: '🤘';
animation: agg-headbang 1.6s ease-in-out infinite;
}
}
// ---------- Titulek dne: lepicí papírek s páskou a razítkem 承認 ----------
.day-navigator .title {
position: relative;
display: inline-block;
padding: 10px 30px 12px;
font-family: var(--agg-display);
font-weight: 800;
font-size: 1.6rem;
color: #3a3220 !important;
background: linear-gradient(170deg, #fff08f, var(--agg-postit));
border-radius: 2px 2px 14px 2px;
box-shadow: 2px 6px 10px rgb(74 93 122 / 0.22);
transform: rotate(-1.5deg);
// Kousek izolepy nahoře
&::before {
content: '';
position: absolute;
left: 50%;
top: -10px;
width: 74px;
height: 20px;
margin-left: -37px;
background: rgb(255 255 255 / 0.55);
border: 1px solid rgb(74 93 122 / 0.12);
transform: rotate(-3deg);
}
// Razítko hanko „schváleno"
&::after {
content: '承認';
position: absolute;
right: -20px;
bottom: -14px;
width: 46px;
height: 46px;
display: grid;
place-items: center;
font-size: 0.42em;
font-weight: 800;
color: var(--agg-hanko);
border: 2px solid var(--agg-hanko);
border-radius: 50%;
background: rgb(255 255 255 / 0.35);
opacity: 0.85;
transform: rotate(-14deg);
}
&.text-muted {
color: #5a6070 !important;
background: linear-gradient(170deg, #eef1f5, #dfe3ea);
// Jiný den — „vyřízeno"
&::after {
content: '済';
font-size: 0.6em;
color: #8a909e;
border-color: #8a909e;
}
}
}
// Rage mode: karaoke obrazovka s rudým textem a mikrofonem
&[data-bs-theme="dark"] .day-navigator .title {
font-family: var(--agg-metal);
font-weight: 400;
letter-spacing: 0.04em;
color: #ff4a3d !important;
text-shadow: 0 0 12px rgb(255 46 63 / 0.75);
background: #000;
border: 2px solid #ff2e3f;
border-radius: 4px;
box-shadow: 0 0 22px rgb(255 46 63 / 0.35);
transform: none;
&::before {
display: none;
}
&::after {
content: '🎤';
right: -16px;
bottom: auto;
top: -16px;
width: auto;
height: auto;
font-size: 0.8em;
border: none;
background: none;
opacity: 1;
transform: rotate(18deg);
}
&.text-muted {
color: #8f8584 !important;
text-shadow: none;
border-color: #3a1c20;
box-shadow: none;
&::after {
content: '💤';
}
}
}
// ---------- Karty podniků: složky s rezavou hlavičkou, při najetí Retsuko vybuchne ----------
.restaurant-card {
border: 1px solid var(--agg-line);
border-radius: var(--luncher-radius-lg);
box-shadow: var(--luncher-shadow);
overflow: hidden;
&:hover {
transform: translateY(-2px);
border-color: var(--agg-line);
box-shadow: var(--luncher-shadow-lg);
}
.restaurant-header {
background:
linear-gradient(rgb(255 255 255 / 0.25), rgb(255 255 255 / 0.25)) left bottom / 100% 1px no-repeat,
linear-gradient(135deg, var(--agg-retsuko), var(--agg-retsuko-deep));
// Při najetí rage — rudá a krátké zatřesení
&:hover {
background: linear-gradient(135deg, #e0283f, #a50d24);
animation: agg-rage 0.35s ease-in-out;
}
small {
color: rgb(255 255 255 / 0.85);
}
}
}
&[data-bs-theme="dark"] .restaurant-card .restaurant-header {
background:
agg-zigzag('%23ff2e3f') left bottom / 16px 8px repeat-x,
#000;
padding-bottom: 22px;
&:hover {
background:
agg-zigzag('%23000000') left bottom / 16px 8px repeat-x,
linear-gradient(135deg, #c8102e, #7a0a1c);
}
}
.food-table tbody tr:hover > td {
background: #fff1e4;
}
&[data-bs-theme="dark"] .food-table tbody tr:hover > td {
background: #221a1b;
}
.food-price {
color: var(--agg-retsuko-deep);
font-weight: 800;
}
// ---------- Panely a tabulka voleb ----------
.choice-section,
.choices-table,
.pizza-section,
.no-votes {
border: 1px solid var(--agg-line);
border-radius: var(--luncher-radius-lg);
box-shadow: var(--luncher-shadow);
}
.choices-table {
border-collapse: separate;
border-spacing: 0;
}
// Sloupec s podnikem: lepicí papírek
.choices-table > tbody > tr > td:first-child {
background: #fff6bf;
color: #3a3220;
font-family: var(--agg-display);
font-weight: 800;
border-right: 1px solid #f0dd8a;
}
&[data-bs-theme="dark"] .choices-table > tbody > tr > td:first-child {
background: #2a0f12;
color: #ff4a3d;
font-family: var(--agg-metal);
font-weight: 400;
letter-spacing: 0.03em;
border-right-color: #3a1c20;
}
// Bootstrap karty (skupinové objednávky apod.) a seznamy
.card {
border: 1px solid var(--agg-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 var(--agg-retsuko);
font-weight: 800;
&:first-child {
border-radius: calc(var(--luncher-radius-lg) - 1px) calc(var(--luncher-radius-lg) - 1px) 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: 1px solid var(--agg-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: 1px solid var(--agg-line);
&:focus {
border-color: var(--agg-retsuko);
box-shadow: 0 0 0 3px rgb(217 103 58 / 0.22);
}
}
// ---------- Tlačítka: kancelářsky slušná, v rage mode rudě žhnou ----------
.btn {
border-radius: var(--luncher-radius-sm);
font-weight: 800;
transition: transform 0.12s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.btn-primary {
background: var(--agg-retsuko);
border: 1px solid var(--agg-retsuko-deep);
color: #fff;
box-shadow: 0 2px 0 var(--agg-retsuko-deep);
&:hover {
background: var(--agg-retsuko-deep);
border-color: var(--agg-retsuko-deep);
color: #fff;
}
&:active {
transform: translateY(2px) !important;
box-shadow: none !important;
}
&:disabled {
background: #e4dcd4;
border-color: #e4dcd4;
color: #8f8584;
box-shadow: none;
}
}
&[data-bs-theme="dark"] .btn-primary {
background: #c8102e;
border-color: #ff2e3f;
box-shadow: 0 0 12px rgb(255 46 63 / 0.45);
&:hover {
background: #e0283f;
box-shadow: 0 0 20px rgb(255 46 63 / 0.7);
}
&:disabled {
background: #2a1f20;
border-color: #2a1f20;
color: #8f8584;
box-shadow: none;
}
}
.btn-outline-primary {
background: var(--luncher-bg-card);
border: 1px solid var(--agg-retsuko);
color: var(--luncher-primary);
&:hover {
background: var(--luncher-primary-light);
border-color: var(--agg-retsuko);
color: var(--luncher-primary);
}
}
.btn-secondary {
background: var(--agg-office);
border: 1px solid var(--agg-office-deep);
color: #fff;
&:hover {
background: var(--agg-office-deep);
color: #fff;
}
}
&[data-bs-theme="dark"] .btn-secondary {
background: #241c1d;
border-color: #3a2a2c;
&:hover {
background: #2f2425;
}
}
.btn-danger {
background: var(--agg-hanko);
border: 1px solid #8f0a20;
}
.action-icon:hover {
background: var(--luncher-primary-light);
color: var(--luncher-primary);
}
// ---------- Modaly: kancelářské okno, v rage mode zubaté ----------
.modal-content {
border: 1px solid var(--agg-line);
border-radius: var(--luncher-radius-xl);
box-shadow: 0 16px 40px rgb(74 93 122 / 0.28);
overflow: hidden;
}
.modal-header {
background:
linear-gradient(var(--agg-retsuko), var(--agg-retsuko)) left bottom / 100% 4px no-repeat,
var(--agg-office);
border-bottom: none;
padding-bottom: calc(var(--bs-modal-header-padding-y, 1rem) + 4px);
.modal-title, .modal-title h2 {
color: #fff;
font-family: var(--agg-display);
font-weight: 800;
}
.btn-close {
filter: invert(1) grayscale(1) brightness(2);
}
}
&[data-bs-theme="dark"] {
.modal-content {
border-color: #ff2e3f;
box-shadow: 0 0 30px rgb(255 46 63 / 0.25);
}
.modal-header {
background:
agg-zigzag('%23ff2e3f') left bottom / 16px 8px repeat-x,
#000;
padding-bottom: calc(var(--bs-modal-header-padding-y, 1rem) + 8px);
.modal-title, .modal-title h2 {
font-family: var(--agg-metal);
font-weight: 400;
letter-spacing: 0.03em;
}
}
}
.footer {
border-top: none;
background-image: linear-gradient(var(--agg-retsuko), var(--agg-retsuko));
background-size: 100% 4px;
background-position: top;
background-repeat: no-repeat;
a {
color: #ffd2b8;
}
}
&[data-bs-theme="dark"] .footer {
background-image: agg-zigzag('%23ff2e3f');
background-size: 16px 8px;
background-repeat: repeat-x;
a {
color: #ff6f63;
}
}
}
// Retsuko vybuchne — krátké zatřesení
@keyframes agg-rage {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-3px) rotate(-0.5deg); }
40% { transform: translateX(3px) rotate(0.5deg); }
60% { transform: translateX(-2px); }
80% { transform: translateX(2px); }
}
// Headbanging u loga v rage mode
@keyframes agg-headbang {
0%, 100% { transform: rotate(-10deg); }
50% { transform: rotate(14deg) translateY(2px); }
}
@media (max-width: 575.98px) {
#{$agg} .day-navigator .title {
font-size: 1.3rem;
padding: 8px 22px 10px;
&::after {
right: -8px;
}
}
}
@media (prefers-reduced-motion: reduce) {
#{$agg} .restaurant-card .restaurant-header:hover,
#{$agg} .navbar .navbar-brand::after {
animation: none !important;
}
}