Merz leck Eier: MTG troll site

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pascal Linxweiler
2026-09-23 15:49:55 +02:00
commit c1cff189cb
15 changed files with 1690 additions and 0 deletions

40
app/CardView.tsx Normal file
View File

@@ -0,0 +1,40 @@
import type { Card } from "./cards";
function Symbol({ s }: { s: string }) {
return <span className={`mana mana-${s === "€" ? "E" : s}`}>{s}</span>;
}
function renderText(line: string) {
return line.split(/(\{[^}]+\})/g).map((part, i) =>
part.startsWith("{") ? <Symbol key={i} s={part.slice(1, -1)} /> : part,
);
}
export function CardView({ card, big = false }: { card: Card; big?: boolean }) {
return (
<div className={`card c-${card.color} ${big ? "big" : ""}`}>
<div className="card-inner">
<div className="bar title">
<span>{card.name}</span>
<span className="cost">
{card.cost.map((c, i) => (
<Symbol key={i} s={c} />
))}
</span>
</div>
<div className="art">{card.art}</div>
<div className="bar type">
<span>{card.type}</span>
<span className={`rarity r-${card.rarity}`}>◆</span>
</div>
<div className="textbox">
{card.text.map((t, i) => (
<p key={i}>{renderText(t)}</p>
))}
{card.flavor && <p className="flavor">{card.flavor}</p>}
</div>
{card.pt && <div className="pt">{card.pt}</div>}
</div>
</div>
);
}

125
app/Game.tsx Normal file
View File

@@ -0,0 +1,125 @@
"use client";
import { useEffect, useState } from "react";
import { CardView } from "./CardView";
import { COMMANDER, DECK, type Card } from "./cards";
const shuffle = <T,>(a: T[]) => [...a].sort(() => Math.random() - 0.5);
const LOG_LINES = [
"Die Umfragewerte sinken. Merz macht eine Pressekonferenz über Bierdeckel.",
"Merz tappt Sauerland. Niemand ist überrascht.",
"Das Volk wirkt „Leck Eier“. Es ist sehr effektiv.",
"Merz versucht zu mulliganen, aber die Koalition sagt nein.",
"BlackRock ruft an. Merz geht natürlich ran.",
"Merz zieht eine Karte. Es ist wieder ein Wahlversprechen. Er wirft es ab.",
];
export function Game() {
const [library, setLibrary] = useState<Card[]>([]);
const [hand, setHand] = useState<Card[]>([]);
const [grave, setGrave] = useState<Card[]>([]);
const [polls, setPolls] = useState(25);
const [eggs, setEggs] = useState(0);
const [log, setLog] = useState<string[]>(["Merz, Leck Eier betritt das Schlachtfeld. Aus einem Privatjet."]);
useEffect(() => {
setLibrary(shuffle(DECK));
try {
setEggs(Number(localStorage.getItem("eggs") ?? 0));
} catch {}
}, []);
const say = (line: string) => setLog((l) => [line, ...l].slice(0, 6));
function draw() {
if (library.length === 0) {
say("Deine Bibliothek ist leer. Wie das Rentenkonzept.");
return;
}
const [top, ...rest] = library;
setLibrary(rest);
setHand((h) => [...h, top]);
say(`Du ziehst „${top.name}“.`);
}
function cast(i: number) {
const card = hand[i];
setHand((h) => h.filter((_, j) => j !== i));
setGrave((g) => [card, ...g]);
const dmg = card.name === "Leck Eier" ? 10 : Math.ceil(Math.random() * 4);
setPolls((p) => Math.max(0, p - dmg));
say(`Du wirkst „${card.name}“. Merz verliert ${dmg}% Zustimmung.`);
}
function mulligan() {
setLibrary(shuffle(DECK));
setHand([]);
setGrave([]);
setPolls(25);
say("Mulligan! Neue Hand, gleicher Kanzler.");
}
function lick() {
const n = eggs + 1;
setEggs(n);
try {
localStorage.setItem("eggs", String(n));
} catch {}
say(LOG_LINES[Math.floor(Math.random() * LOG_LINES.length)]);
}
return (
<>
<section className="board">
<div className="commander">
<div className="label">Commander</div>
<CardView card={COMMANDER} big />
<div className="life">
<span>Umfragewerte</span>
<strong className={polls === 0 ? "dead" : ""}>{polls}%</strong>
<div className="meter">
<div style={{ width: `${(polls / 25) * 100}%` }} />
</div>
{polls === 0 && <p className="win">🏆 Du hast gewonnen. Neuwahlen!</p>}
</div>
</div>
<div className="side">
<div className="buttons">
<button onClick={draw}>🃏 Karte ziehen ({library.length})</button>
<button onClick={mulligan} className="ghost">
🔄 Mulligan
</button>
<button onClick={lick} className="egg">
🥚 Leck Eier ({eggs})
</button>
</div>
<ol className="log">
{log.map((l, i) => (
<li key={i + l}>{l}</li>
))}
</ol>
<div className="grave">
⚰️ Friedhof: {grave.length ? grave.map((c) => c.name).join(", ") : "leer (wie die Staatskasse)"}
</div>
</div>
</section>
<section>
<h2>Deine Hand {hand.length > 0 && <small>— klick eine Karte zum Wirken</small>}</h2>
{hand.length === 0 ? (
<p className="empty">Noch keine Karten. Zieh eine, bevor Merz es privatisiert.</p>
) : (
<div className="hand">
{hand.map((c, i) => (
<button key={i + c.name} className="card-btn" onClick={() => cast(i)}>
<CardView card={c} />
</button>
))}
</div>
)}
</section>
</>
);
}

162
app/cards.ts Normal file
View File

@@ -0,0 +1,162 @@
export type Color = "B" | "W" | "G" | "R" | "U" | "C" | "gold";
export type Card = {
name: string;
cost: string[];
type: string;
text: string[];
flavor?: string;
pt?: string;
color: Color;
rarity: "common" | "uncommon" | "rare" | "mythic";
art: string;
};
export const COMMANDER: Card = {
name: "Merz, Leck Eier",
cost: ["€", "€", "€", "B"],
type: "Legendäre Kreatur — Sauerländer Millionär",
text: [
"Fliegend (nur im Privatjet)",
"Bierdeckel — {T}: Jeder Spieler mit mehr als einer Million Lebenspunkten zahlt keine Steuern.",
"Wenn Merz, Leck Eier ins Spiel kommt, vergiss alle Wahlversprechen.",
],
flavor: "„Ich gehöre zur gehobenen Mittelschicht.“",
pt: "1/1.000.000",
color: "gold",
rarity: "mythic",
art: "🧓✈️💶",
};
export const DECK: Card[] = [
{
name: "Schuldenbremse lösen",
cost: ["X", "U"],
type: "Spontanzauber",
text: [
"Tu so, als hättest du nie etwas anderes gesagt.",
"Ziehe X Karten, wobei X gleich 500 Milliarden ist.",
],
flavor: "Im Wahlkampf war das noch ein Konter.",
color: "U",
rarity: "rare",
art: "🔓💸",
},
{
name: "BlackRock-Aufsichtsrat",
cost: ["4", "B"],
type: "Artefakt",
text: [
"Zu Beginn deines Versorgungssegments erhältst du einen Aktienspielstein.",
"Opfere einen Rentner: Erhalte 3 Aktienspielsteine.",
],
flavor: "Unabhängig von Lobbyinteressen. Garantiert.",
color: "B",
rarity: "rare",
art: "🏦📈",
},
{
name: "Privatjet zur Hochzeit",
cost: ["6"],
type: "Artefakt — Fahrzeug",
text: ["Fliegend", "Besatzung 1 (nur Liberale)", "Sylt ist ein legales Ziel für diese Karte."],
flavor: "Die Bahn war leider ausgebucht. Seit 1994.",
pt: "4/4",
color: "C",
rarity: "uncommon",
art: "🛩️💍",
},
{
name: "Bierdeckel",
cost: ["1"],
type: "Artefakt — Ausrüstung",
text: [
"Die ausgerüstete Kreatur kann ihre Steuererklärung in einem Zug machen.",
"Ausrüsten {0}. Nur für Spitzenverdiener.",
],
flavor: "Seit 2003 in Entwicklung.",
color: "C",
rarity: "common",
art: "🍺🧾",
},
{
name: "Brandmauer",
cost: ["2", "W"],
type: "Kreatur — Mauer",
text: [
"Verteidiger",
"Die Brandmauer hat Widerstandskraft 0, solange eine Abstimmung im Januar stattfindet.",
],
flavor: "Stabil wie ein Bierdeckel im Regen.",
pt: "0/10",
color: "W",
rarity: "common",
art: "🧱🔥",
},
{
name: "Sauerland",
cost: [],
type: "Standardland — Sauerland",
text: ["{T}: Erzeuge {€}{€}.", "Sauerland kommt getappt ins Spiel, genau wie der Kanzler."],
color: "G",
rarity: "common",
art: "🌲🏔️",
},
{
name: "Leck Eier",
cost: ["R", "R"],
type: "Spontanzauber",
text: [
"Neutralisiere einen Wahlversprechen-Zauber deiner Wahl.",
"Rückblende {Volksmund}",
],
flavor: "— Die Bevölkerung, einstimmig",
color: "R",
rarity: "mythic",
art: "🥚🥚",
},
{
name: "Friedrichs Kaffeefahrt",
cost: ["3", "B"],
type: "Hexerei",
text: [
"Jeder Gegner wirft eine Karte ab und kauft eine Heizdecke.",
"Du erhältst 2 Lebenspunkte pro verkaufter Heizdecke.",
],
color: "B",
rarity: "uncommon",
art: "☕🛋️",
},
{
name: "Agenda 2030 Rückkehr",
cost: ["2", "W", "B"],
type: "Verzauberung",
text: [
"Alle Arbeiter-Kreaturen erhalten -1/-1 und Wachsamkeit (Überstunden).",
"Kreaturen mit Stärke unter 1.000.000 können nicht angreifen.",
],
flavor: "„Wir müssen wieder mehr arbeiten.“ — gesagt von einem Aufsichtsrat.",
color: "gold",
rarity: "rare",
art: "⏰🛠️",
},
{
name: "Kanzlertunnel",
cost: ["5", "U", "U"],
type: "Hexerei",
text: ["Du hast diesen Zug keine Ahnung, was im Land passiert.", "Nimm einen zusätzlichen Zug nach Davos."],
color: "U",
rarity: "rare",
art: "🕳️🏔️",
},
{
name: "Eierlikör der Einheit",
cost: ["G"],
type: "Spontanzauber",
text: ["Alle Spieler erhalten 1 Lebenspunkt und vergessen die letzte Regierungserklärung."],
flavor: "Wirkt besser als jede Reform.",
color: "G",
rarity: "common",
art: "🥃🥚",
},
];

137
app/globals.css Normal file
View File

@@ -0,0 +1,137 @@
@import url("https://fonts.googleapis.com/css2?family=Cinzel:wght@700;900&family=Crimson+Text:ital,wght@0,400;0,700;1,400&display=swap");
:root {
--bg: #120d0a;
--fg: #f3e9d2;
--muted: #b8a88a;
--gold: #e8b64c;
--red: #d8452f;
}
* { box-sizing: border-box; }
html, body { margin: 0; }
body {
background:
radial-gradient(ellipse at top, #3a2413 0%, transparent 60%),
repeating-linear-gradient(45deg, #140f0b 0 12px, #110c09 12px 24px);
background-color: var(--bg);
color: var(--fg);
font-family: "Crimson Text", Georgia, serif;
font-size: 18px;
min-height: 100vh;
}
main { max-width: 1100px; margin: 0 auto; padding: 24px 16px 64px; }
h1, h2, .bar, button, .label { font-family: "Cinzel", serif; }
h2 { color: var(--gold); margin: 48px 0 16px; font-size: 1.5rem; }
h2 small { color: var(--muted); font-size: 0.9rem; font-weight: 400; }
.hero { text-align: center; padding: 32px 0 16px; }
.kicker { color: var(--muted); text-transform: uppercase; letter-spacing: 0.15em; font-size: 0.8rem; }
.hero h1 {
font-size: clamp(3rem, 12vw, 7rem);
margin: 8px 0;
line-height: 1;
background: linear-gradient(180deg, #fff3c4, var(--gold) 55%, #8a5a12);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 4px 0 #000);
}
.hero h1 span { font-style: italic; color: var(--red); -webkit-text-fill-color: var(--red); }
.sub { max-width: 620px; margin: 0 auto; color: var(--muted); font-size: 1.15rem; }
/* board */
.board { display: grid; grid-template-columns: auto 1fr; gap: 32px; margin-top: 32px; align-items: start; }
@media (max-width: 760px) { .board { grid-template-columns: 1fr; justify-items: center; } }
.label { color: var(--gold); text-align: center; margin-bottom: 8px; letter-spacing: 0.2em; font-size: 0.8rem; }
.life { margin-top: 16px; text-align: center; }
.life span { color: var(--muted); display: block; font-size: 0.9rem; }
.life strong { font-family: "Cinzel", serif; font-size: 2.5rem; color: var(--gold); }
.life strong.dead { color: var(--red); }
.meter { height: 10px; background: #2b1e14; border-radius: 99px; overflow: hidden; margin-top: 4px; }
.meter div { height: 100%; background: linear-gradient(90deg, var(--red), var(--gold)); transition: width 0.4s; }
.win { color: var(--gold); font-size: 1.3rem; animation: pulse 1s infinite alternate; }
@keyframes pulse { to { transform: scale(1.08); } }
.side { width: 100%; }
.buttons { display: flex; flex-wrap: wrap; gap: 12px; }
button {
cursor: pointer; border: 2px solid var(--gold); background: linear-gradient(#5a3b16, #2e1d0a);
color: var(--fg); padding: 12px 18px; border-radius: 10px; font-size: 1rem; font-weight: 700;
transition: transform 0.1s;
}
button:hover { transform: translateY(-2px); }
button:active { transform: translateY(1px); }
button.ghost { background: transparent; }
button.egg { border-color: var(--red); background: linear-gradient(#7a2517, #3c0f08); }
.log { list-style: none; padding: 16px; margin: 16px 0; background: #0009; border: 1px solid #4a3620; border-radius: 10px; min-height: 180px; }
.log li { padding: 4px 0; border-bottom: 1px dashed #3a2a18; color: var(--muted); }
.log li:first-child { color: var(--fg); }
.grave { color: var(--muted); font-style: italic; }
.empty { color: var(--muted); font-style: italic; }
.hand, .gallery { display: flex; flex-wrap: wrap; gap: 16px; }
.gallery { justify-content: center; }
.card-btn { all: unset; cursor: pointer; transition: transform 0.15s; }
.card-btn:hover { transform: translateY(-10px) rotate(-2deg); }
.card-btn:focus-visible { outline: 3px solid var(--gold); border-radius: 14px; }
/* card */
.card {
--frame: #bbb; --box: #eee;
width: 250px; aspect-ratio: 63 / 88; padding: 10px; border-radius: 14px;
background: #111; box-shadow: 0 10px 30px #000a; color: #111; font-size: 13px;
}
.card.big { width: 300px; font-size: 15px; }
.card-inner {
height: 100%; display: flex; flex-direction: column; gap: 6px; padding: 8px;
border-radius: 8px; background: var(--frame); position: relative;
}
.bar {
display: flex; justify-content: space-between; align-items: center; gap: 6px;
background: linear-gradient(#fff8, #fff2); border: 2px solid #0006; border-radius: 6px;
padding: 3px 7px; font-weight: 700; font-size: 0.85em; box-shadow: inset 0 1px 0 #fff8;
}
.title span:first-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cost { display: flex; gap: 2px; flex-shrink: 0; }
.art {
flex: 1 1 42%; min-height: 0; display: grid; place-items: center; font-size: 3.4em;
border: 2px solid #0008; border-radius: 3px;
background: radial-gradient(circle at 30% 30%, #fff6, transparent 60%), linear-gradient(135deg, #0003, #0000), var(--box);
}
.textbox {
flex: 1 1 40%; background: #f6efdd; border: 2px solid #0006; border-radius: 3px;
padding: 6px 8px; overflow: auto; line-height: 1.25;
}
.textbox p { margin: 0 0 5px; }
.flavor { font-style: italic; color: #444; border-top: 1px solid #0002; padding-top: 4px; }
.pt {
position: absolute; right: 6px; bottom: 2px; background: var(--frame); border: 2px solid #0008;
border-radius: 6px; padding: 1px 8px; font-family: "Cinzel", serif; font-weight: 900; font-size: 0.9em;
}
.rarity { font-size: 1.1em; }
.r-common { color: #111; } .r-uncommon { color: #9fb3c0; } .r-rare { color: #d4a938; }
.r-mythic { color: #e0561b; text-shadow: 0 0 4px #ff9a3c; }
.mana {
display: inline-grid; place-items: center; width: 1.35em; height: 1.35em; border-radius: 50%;
font-family: "Cinzel", serif; font-weight: 900; font-size: 0.8em; color: #111;
background: #cbc2bf; border: 1px solid #0007; vertical-align: middle; margin: 0 1px;
}
.mana-B { background: #a69f9d; } .mana-W { background: #fffbd5; } .mana-U { background: #aae0fa; }
.mana-R { background: #f9aa8f; } .mana-G { background: #9bd3ae; } .mana-E { background: #f2c94c; }
.mana-T { background: #cbc2bf; }
.c-B { --frame: linear-gradient(160deg, #4a4543, #1d1a19); --box: #6b5f5a; }
.c-W { --frame: linear-gradient(160deg, #f5f0dc, #cfc6a8); --box: #efe6c8; }
.c-U { --frame: linear-gradient(160deg, #3d7fb8, #1a4166); --box: #79add6; }
.c-R { --frame: linear-gradient(160deg, #d0492f, #7a1f10); --box: #ec8a6b; }
.c-G { --frame: linear-gradient(160deg, #3d8a55, #1a4a2a); --box: #7cbf8e; }
.c-C { --frame: linear-gradient(160deg, #b9b3ad, #7b746d); --box: #ccc6be; }
.c-gold { --frame: linear-gradient(160deg, #f0d27a, #a67a1c); --box: #f5dc92; }
.c-B .pt, .c-U .pt, .c-R .pt, .c-G .pt { color: #fff; }
.rules ol { color: var(--muted); line-height: 1.7; }
footer { margin-top: 64px; padding-top: 24px; border-top: 1px solid #3a2a18; color: #7d6e55; font-size: 0.85rem; text-align: center; }

20
app/layout.tsx Normal file
View File

@@ -0,0 +1,20 @@
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "Merz leck Eier",
description: "Das offizielle Commander-Deck der Bundesregierung. Satire.",
icons: { icon: "/favicon.svg" },
openGraph: {
title: "Merz leck Eier 🥚🥚",
description: "Legendäre Kreatur — Sauerländer Millionär. Satire.",
},
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="de">
<body>{children}</body>
</html>
);
}

46
app/page.tsx Normal file
View File

@@ -0,0 +1,46 @@
import { CardView } from "./CardView";
import { DECK } from "./cards";
import { Game } from "./Game";
export default function Home() {
return (
<main>
<header className="hero">
<p className="kicker">Commander-Format · Bundestagsedition · Nicht turnierlegal</p>
<h1>
Merz <span>leck</span> Eier
</h1>
<p className="sub">
Das einzige Magic-Deck, in dem der Commander mit 1.000.000 Widerstandskraft startet und trotzdem jede
Abstimmung verliert.
</p>
</header>
<Game />
<section>
<h2>Die komplette Decklist</h2>
<div className="gallery">
{DECK.map((c) => (
<CardView key={c.name} card={c} />
))}
</div>
</section>
<section className="rules">
<h2>Regeln</h2>
<ol>
<li>Merz startet mit 25% Umfragewerten. Bring sie auf 0.</li>
<li>Jede gewirkte Karte kostet ihn Zustimmung. „Leck Eier“ kostet ihn 10%.</li>
<li>Merz darf keinen Mulligan nehmen. Das darfst nur du.</li>
<li>Bierdeckel zählen nicht als Steuererklärung (außer für Merz).</li>
</ol>
</section>
<footer>
Satire. Keine echten Zitate außer denen, die er wirklich gesagt hat. Magic: The Gathering ist eine Marke von
Wizards of the Coast — die haben damit nichts zu tun und wollen es auch nicht.
</footer>
</main>
);
}