Merge branch 'main' of git.bizmatch.net:tknuth/entscheidomat
This commit is contained in:
@@ -91,14 +91,15 @@ export default function DatenschutzPage() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>5. Cookies und Werbung</h2>
|
||||
<h2>5. Werbung mit Google AdSense</h2>
|
||||
<p className="mt-3">
|
||||
Entscheidomat setzt aktuell keine Cookies und keine Werbedienste
|
||||
ein. Sollte künftig Werbung (z. B. Google AdSense)
|
||||
hinzukommen, wird diese Datenschutzerklärung vorher aktualisiert
|
||||
und eine Einwilligungslösung (Consent-Banner) für Nutzerinnen und
|
||||
Nutzer aus der EU, dem UK und der Schweiz eingebaut, bevor der
|
||||
Dienst aktiv geschaltet wird.
|
||||
Entscheidomat ist für die Nutzung von Google AdSense vorbereitet.
|
||||
Sobald personalisierte oder nicht-personalisierte Anzeigen an
|
||||
Besucherinnen und Besucher im Europäischen Wirtschaftsraum, im
|
||||
Vereinigten Königreich oder in der Schweiz ausgeliefert werden,
|
||||
setzen wir eine geeignete Einwilligungslösung ein und aktualisieren
|
||||
diese Datenschutzerklärung um die dann konkret verwendeten
|
||||
Einstellungen und Empfänger.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
||||
190
app/developer/page.tsx
Normal file
190
app/developer/page.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { ArrowUpRight, Box, Check, Code2, Puzzle, Terminal } from "lucide-react";
|
||||
import Nav from "@/components/nav";
|
||||
import Footer from "@/components/footer";
|
||||
|
||||
function GithubIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
|
||||
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 0 6 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Decision Kit – Developer Suite",
|
||||
description: "Open-Source-Tools von Entscheidomat: Repository, VS Code Extension, Chrome Extension, npm-Paket, Python SDK und GitHub Action.",
|
||||
openGraph: {
|
||||
title: "Decision Kit – Developer Suite | Entscheidomat",
|
||||
description: "Zufalls-Entscheidungstools für VS Code, Chrome, Node.js, Python und GitHub Actions.",
|
||||
url: "https://entscheidomat.com/developer",
|
||||
},
|
||||
};
|
||||
|
||||
type Asset = {
|
||||
title: string;
|
||||
eyebrow: string;
|
||||
description: string;
|
||||
icon: typeof Puzzle;
|
||||
action?: string;
|
||||
href?: string;
|
||||
snippet?: string;
|
||||
status: string;
|
||||
};
|
||||
|
||||
const assets: Asset[] = [
|
||||
{
|
||||
title: "GitHub Repository",
|
||||
eyebrow: "Open Source · MIT",
|
||||
description: "Der vollständige Decision-Kit-Quellcode mit Releases, Issues und Contribution Guide – die zentrale Anlaufstelle für Entwickler.",
|
||||
icon: Code2,
|
||||
action: "Repository öffnen",
|
||||
href: "https://github.com/knuthtimo-lab/decision-kit",
|
||||
snippet: "git clone https://github.com/knuthtimo-lab/decision-kit.git",
|
||||
status: "knuthtimo-lab/decision-kit",
|
||||
},
|
||||
{
|
||||
title: "Decision Kit für VS Code",
|
||||
eyebrow: "Extension · v1.0.0",
|
||||
description: "Ziehe einen Gewinner aus markiertem Text, würfle mit Notation wie 2d6+3 und öffne das Glücksrad direkt in deiner IDE.",
|
||||
icon: Puzzle,
|
||||
action: "Im VS Code Store öffnen",
|
||||
href: "https://marketplace.visualstudio.com/items?itemName=TimoKnuth.decisionkit-vscode",
|
||||
status: "Visual Studio Marketplace",
|
||||
},
|
||||
{
|
||||
title: "Decision Kit für Chrome",
|
||||
eyebrow: "Browser-Erweiterung · Manifest V3",
|
||||
description: "Random Picker, Glücksrad, Würfel, Münzwurf und praktische Kontextmenü-Kürzel für schnelle Entscheidungen im Browser.",
|
||||
icon: Puzzle,
|
||||
action: "Im Chrome Web Store ansehen",
|
||||
href: "https://chromewebstore.google.com/detail/decision-kit-random-picke/ehnieppgcoaapbgnilajkcjhakjedlgo",
|
||||
status: "Chrome Web Store · Live",
|
||||
},
|
||||
{
|
||||
title: "Decision Kit für Firefox",
|
||||
eyebrow: "Firefox Add-on · v1.1.5",
|
||||
description: "Random Picker, Glücksrad, Würfel, Münzwurf und Kontextmenü-Kürzel direkt im Firefox-Browser nutzen.",
|
||||
icon: Puzzle,
|
||||
action: "Auf Firefox Add-ons ansehen",
|
||||
href: "https://addons.mozilla.org/addon/decision-kit-picker-rad-w%C3%BCrfel/",
|
||||
status: "Mozilla Add-ons · Live",
|
||||
},
|
||||
{
|
||||
title: "TypeScript Library",
|
||||
eyebrow: "npm · v1.0.0",
|
||||
description: "Zufallsalgorithmen, Würfel-Parser, Glücksrad-Logik und React Hooks als wiederverwendbare Bausteine.",
|
||||
icon: Box,
|
||||
action: "npm-Paket ansehen",
|
||||
href: "https://www.npmjs.com/package/decision-kit",
|
||||
snippet: "npm install decision-kit",
|
||||
status: "Node.js & React",
|
||||
},
|
||||
{
|
||||
title: "Python SDK & CLI",
|
||||
eyebrow: "PyPI · v1.0.0",
|
||||
description: "Decision-Kit für Python-Skripte, Automatisierungen und die Kommandozeile.",
|
||||
icon: Terminal,
|
||||
action: "PyPI-Projekt ansehen",
|
||||
href: "https://pypi.org/project/decision-kit/",
|
||||
snippet: "pip install decision-kit",
|
||||
status: "Python Package Index",
|
||||
},
|
||||
{
|
||||
title: "GitHub Action",
|
||||
eyebrow: "GitHub Marketplace",
|
||||
description: "Nutze Auslosungen, Teamaufteilungen und Zufallsentscheidungen direkt in deinen CI/CD-Workflows.",
|
||||
icon: Code2,
|
||||
action: "Action ansehen",
|
||||
href: "https://github.com/marketplace/actions/decision-kit-action",
|
||||
snippet: "uses: knuthtimo-lab/decision-kit@v1.0.0",
|
||||
status: "Für GitHub Actions",
|
||||
},
|
||||
];
|
||||
|
||||
export default function DeveloperPage() {
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
name: "Decision Kit",
|
||||
operatingSystem: "All",
|
||||
applicationCategory: "DeveloperApplication",
|
||||
author: { "@type": "Organization", name: "Entscheidomat" },
|
||||
codeRepository: "https://github.com/knuthtimo-lab/decision-kit",
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }} />
|
||||
<Nav />
|
||||
<main className="flex-1 bg-paper pb-20 pt-28 sm:pt-32">
|
||||
<div className="mx-auto w-full max-w-6xl px-5 sm:px-8">
|
||||
<header className="max-w-3xl">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[#0d7a57]">Entscheidomat für Entwickler</p>
|
||||
<h1 className="mt-4 text-balance font-display text-5xl font-semibold leading-[0.9] tracking-[-0.045em] text-ink sm:text-6xl">Decision Kit, dort wo du arbeitest.</h1>
|
||||
<p className="mt-6 max-w-2xl text-base leading-7 text-ink-soft sm:text-lg">Eine offene Sammlung von Tools für faire Zufallsentscheidungen – im Editor, im Browser, im Terminal und im Workflow.</p>
|
||||
</header>
|
||||
|
||||
<section className="relative mt-12 overflow-hidden rounded-[2rem] bg-[#0b241b] px-6 py-7 text-white shadow-[0_22px_60px_rgb(13_122_87/0.22)] sm:px-10 sm:py-10">
|
||||
<div className="pointer-events-none absolute -right-24 -top-36 h-80 w-80 rounded-full bg-[#38b98c]/25 blur-3xl" />
|
||||
<div className="relative grid gap-8 lg:grid-cols-[1.25fr_0.75fr] lg:items-end">
|
||||
<div>
|
||||
<div className="inline-flex items-center gap-2 text-sm font-semibold text-emerald-200"><GithubIcon className="h-4 w-4" />Open Source · MIT-Lizenz</div>
|
||||
<h2 className="mt-5 max-w-xl text-3xl font-semibold tracking-tight sm:text-4xl">Der gesamte Code lebt auf GitHub.</h2>
|
||||
<p className="mt-4 max-w-xl text-sm leading-6 text-emerald-50/75 sm:text-base">Quellcode, Issues, Releases und Contribution Guide findest du im offiziellen Decision-Kit-Repository.</p>
|
||||
<a href="https://github.com/knuthtimo-lab/decision-kit" target="_blank" rel="noopener noreferrer" className="mt-7 inline-flex items-center gap-2 rounded-full bg-white px-5 py-3 text-sm font-semibold text-[#0b241b] transition duration-200 hover:-translate-y-0.5 hover:bg-emerald-50 active:translate-y-0">
|
||||
GitHub-Repository öffnen <ArrowUpRight className="h-4 w-4" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-white/15 bg-black/20 p-5 font-mono text-xs leading-6 text-emerald-50 shadow-inner">
|
||||
<p className="text-emerald-200/70"># Starte lokal</p>
|
||||
<p className="mt-2 break-all">git clone https://github.com/knuthtimo-lab/decision-kit.git</p>
|
||||
<div className="my-4 h-px bg-white/10" />
|
||||
<p className="text-emerald-200/70"># Oder als Paket</p>
|
||||
<p className="mt-2">npm install decision-kit</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-16" aria-labelledby="integrations-heading">
|
||||
<div className="flex flex-col justify-between gap-4 border-b border-line pb-5 sm:flex-row sm:items-end">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[#0d7a57]">Integrationen</p>
|
||||
<h2 id="integrations-heading" className="mt-2 text-2xl font-semibold tracking-tight text-ink sm:text-3xl">Installieren, einbinden, loslegen.</h2>
|
||||
</div>
|
||||
<p className="max-w-sm text-sm leading-6 text-ink-soft">Jede Integration ist unabhängig nutzbar und verweist direkt auf ihren offiziellen Kanal.</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid gap-4 md:grid-cols-2">
|
||||
{assets.map((asset) => {
|
||||
const Icon = asset.icon;
|
||||
return (
|
||||
<article key={asset.title} className="group flex min-h-64 flex-col rounded-3xl border border-line bg-surface p-6 transition duration-200 hover:-translate-y-1 hover:border-[#0d7a57]/40 hover:shadow-lift">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-[#e6f7f0] text-[#0d7a57]"><Icon className="h-5 w-5" /></div>
|
||||
<p className="pt-1 text-right text-[11px] font-semibold uppercase tracking-[0.12em] text-[#0d7a57]">{asset.eyebrow}</p>
|
||||
</div>
|
||||
<h3 className="mt-6 text-xl font-semibold tracking-tight text-ink">{asset.title}</h3>
|
||||
<p className="mt-2 max-w-md text-sm leading-6 text-ink-soft">{asset.description}</p>
|
||||
{asset.snippet && <code className="mt-5 block overflow-x-auto rounded-xl bg-[#111412] px-3.5 py-3 font-mono text-xs text-emerald-100">{asset.snippet}</code>}
|
||||
<div className="mt-auto flex items-center justify-between gap-4 pt-6">
|
||||
<span className="inline-flex items-center gap-1.5 text-xs font-medium text-ink-soft"><Check className="h-3.5 w-3.5 text-[#0d7a57]" />{asset.status}</span>
|
||||
{asset.href && asset.action ? (
|
||||
<a href={asset.href} target="_blank" rel="noopener noreferrer" className="inline-flex shrink-0 items-center gap-1.5 text-sm font-semibold text-[#0d7a57] transition hover:text-[#095c41] hover:underline">{asset.action} <ArrowUpRight className="h-4 w-4" /></a>
|
||||
) : <span className="text-xs font-semibold text-[#0d7a57]">Bald verfügbar</span>}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="mt-14 border-t border-line pt-8 text-sm text-ink-soft">Zurück zu den <Link href="/" className="font-semibold text-[#0d7a57] hover:underline">Entscheidungstools</Link>.</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
169
app/entweder-oder/page.tsx
Normal file
169
app/entweder-oder/page.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import EntwederOder from "@/components/tools/entweder-oder";
|
||||
import SubpageLayout from "@/components/subpage-layout";
|
||||
|
||||
const SITE_URL = "https://entscheidomat.com";
|
||||
const PAGE_URL = `${SITE_URL}/entweder-oder`;
|
||||
const PAGE_TITLE = "Entweder-oder-Generator – zwischen zwei Optionen entscheiden";
|
||||
const PAGE_DESCRIPTION =
|
||||
"Entweder oder? Trage zwei Optionen ein und lass den Generator zufällig entscheiden. Als Entscheidungsrad für zwei Möglichkeiten – kostenlos und ohne Anmeldung.";
|
||||
const UPDATED = "3. August 2026";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: PAGE_TITLE,
|
||||
description: PAGE_DESCRIPTION,
|
||||
alternates: { canonical: "/entweder-oder" },
|
||||
openGraph: { type: "website", locale: "de_DE", siteName: "Entscheidomat", title: PAGE_TITLE, description: PAGE_DESCRIPTION, url: PAGE_URL, images: ["/opengraph-image"] },
|
||||
twitter: { card: "summary_large_image", title: PAGE_TITLE, description: PAGE_DESCRIPTION, images: ["/twitter-image"] },
|
||||
};
|
||||
|
||||
const FAQS = [
|
||||
{
|
||||
q: "Wie entscheidet der Entweder-oder-Generator?",
|
||||
a: "Bei jedem Durchlauf erzeugt das Tool im Browser einen neuen Zufallswert. Beide Optionen haben dieselbe Chance – unabhängig davon, welche du zuerst eingetragen hast oder wie oft eine Option zuletzt gewonnen hat.",
|
||||
},
|
||||
{
|
||||
q: "Kann ich eigene Optionen eintragen?",
|
||||
a: "Ja. Trage in beide Felder frei wählbare Texte mit bis zu 28 Zeichen ein, zum Beispiel „Italiener“ und „Sushi“ oder „heute“ und „morgen“. Ohne Eingabe entscheidet das Tool zwischen „Option A“ und „Option B“.",
|
||||
},
|
||||
{
|
||||
q: "Was ist der Unterschied zum Münzwurf?",
|
||||
a: "Technisch nichts – beide wählen zufällig eine von zwei Möglichkeiten. Der Entweder-oder-Generator zeigt deine Optionen aber direkt als Text an, statt sie auf Kopf und Zahl zu übertragen. Das erspart die Frage, wofür welche Seite stand.",
|
||||
},
|
||||
{
|
||||
q: "Was mache ich bei mehr als zwei Optionen?",
|
||||
a: "Dann ist ein Glücksrad übersichtlicher, weil du dort bis zu zwölf Möglichkeiten gleichzeitig siehst. Bei längeren Namenslisten passt die Namen-Auslosung besser.",
|
||||
},
|
||||
{
|
||||
q: "Eignet sich der Generator für wichtige Entscheidungen?",
|
||||
a: "Nein. Er ist für folgenarme Alltagsentscheidungen gedacht. Bei Gesundheit, Geld, rechtlichen Fragen oder Entscheidungen, die andere Menschen mittragen, gehört eine echte Abwägung davor – und bei Bedarf fachlicher Rat.",
|
||||
},
|
||||
];
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@graph": [
|
||||
{
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: [
|
||||
{ "@type": "ListItem", position: 1, name: "Entscheidomat", item: SITE_URL },
|
||||
{ "@type": "ListItem", position: 2, name: "Entweder oder", item: PAGE_URL },
|
||||
],
|
||||
},
|
||||
{
|
||||
"@type": "WebApplication",
|
||||
name: "Entweder-oder-Generator",
|
||||
url: PAGE_URL,
|
||||
description: PAGE_DESCRIPTION,
|
||||
applicationCategory: "UtilitiesApplication",
|
||||
operatingSystem: "Any (Web-Browser)",
|
||||
isPartOf: { "@id": `${SITE_URL}/#website` },
|
||||
offers: { "@type": "Offer", price: "0", priceCurrency: "EUR" },
|
||||
},
|
||||
{
|
||||
"@type": "FAQPage",
|
||||
mainEntity: FAQS.map((f) => ({
|
||||
"@type": "Question",
|
||||
name: f.q,
|
||||
acceptedAnswer: { "@type": "Answer", text: f.a },
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const STEPS = [
|
||||
"Trage deine beiden Optionen in die Felder ein (bis 28 Zeichen).",
|
||||
"Tippe auf die Karte oder den Button „Entweder oder?“.",
|
||||
"Der Generator wählt zufällig eine der beiden Optionen aus.",
|
||||
"Das Ergebnis erscheint auf der Karte, der Verlauf zeigt die letzten 10 Durchläufe.",
|
||||
];
|
||||
|
||||
const EXAMPLES = [
|
||||
"„Italiener oder Sushi?“",
|
||||
"„Heute erledigen oder morgen?“",
|
||||
"„Bahn oder Auto?“",
|
||||
];
|
||||
|
||||
const VORTEILE = (
|
||||
<p>
|
||||
Zwei Optionen im Klartext statt Kopf und Zahl – dadurch entfällt die Zuordnung
|
||||
und damit der häufigste Streitpunkt nach der Entscheidung. Für mehr als zwei
|
||||
Möglichkeiten eignet sich das{" "}
|
||||
<Link href="/gluecksrad" className="text-[#c97d60] font-medium hover:underline underline-offset-2">
|
||||
Glücksrad
|
||||
</Link>
|
||||
, für eine formulierte Frage der{" "}
|
||||
<Link href="/ja-nein-generator" className="text-[#c97d60] font-medium hover:underline underline-offset-2">
|
||||
Ja-Nein-Generator
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
);
|
||||
|
||||
const ADDITIONAL_CONTENT = (
|
||||
<article className="space-y-4 text-base leading-relaxed text-ink-soft">
|
||||
<h2 className="font-display text-2xl sm:text-3xl font-semibold text-ink">
|
||||
Wann ein Entweder-oder-Generator sinnvoll ist
|
||||
</h2>
|
||||
<p>
|
||||
Nicht jede Pattsituation braucht eine Entscheidungshilfe. Sinnvoll wird der
|
||||
Zufall dann, wenn beide Optionen vertretbar sind und weiteres Abwägen das
|
||||
Ergebnis nicht mehr besser macht. Genau das ist bei kleinen
|
||||
Alltagsentscheidungen fast immer der Fall: Beide Wege sind umkehrbar, und
|
||||
die Unterschiede sind kleiner als der Aufwand, sie weiter auszuleuchten.
|
||||
</p>
|
||||
<p>
|
||||
Wie du erkennst, ob deine Frage so weit ist – und wie du den Impuls nach dem
|
||||
Ergebnis richtig liest – steht im Ratgeber{" "}
|
||||
<Link href="/ratgeber/entweder-oder-entscheiden" className="text-[#c97d60] font-medium hover:underline underline-offset-2">
|
||||
Entweder oder: Wie du dich zwischen zwei Optionen entscheidest
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
<h2 className="font-display text-2xl sm:text-3xl font-semibold text-ink">
|
||||
Entscheidungsrad für zwei Optionen
|
||||
</h2>
|
||||
<p>
|
||||
Ein klassisches Entscheidungsrad lohnt sich ab etwa drei Möglichkeiten. Bei
|
||||
genau zwei Optionen ist eine direkte Ziehung schneller und besser lesbar –
|
||||
das Rad hätte hier nur zwei große Segmente. Sobald eine dritte Möglichkeit
|
||||
dazukommt, wechsle zum{" "}
|
||||
<Link href="/gluecksrad" className="text-[#c97d60] font-medium hover:underline underline-offset-2">
|
||||
Glücksrad
|
||||
</Link>
|
||||
. Geht es dagegen um eine Frage mit Ja oder Nein als Antwort, ist der{" "}
|
||||
<Link href="/ja-nein-generator" className="text-[#c97d60] font-medium hover:underline underline-offset-2">
|
||||
Ja-Nein-Generator
|
||||
</Link>{" "}
|
||||
die direktere Wahl.
|
||||
</p>
|
||||
</article>
|
||||
);
|
||||
|
||||
export default function EntwederOderPage() {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(jsonLd).replace(/</g, "\\u003c"),
|
||||
}}
|
||||
/>
|
||||
<SubpageLayout
|
||||
toolSlug="entweder-oder"
|
||||
toolName="Entweder-oder-Generator"
|
||||
h1Title="Entweder-oder-Generator – zwischen zwei Optionen entscheiden"
|
||||
introText="Entweder oder? Trage deine beiden Möglichkeiten ein und lass den Generator zufällig entscheiden. Beide Optionen haben dieselbe Chance – kostenlos, ohne Anmeldung und direkt im Browser."
|
||||
steps={STEPS}
|
||||
examples={EXAMPLES}
|
||||
vorteileText={VORTEILE}
|
||||
additionalContent={ADDITIONAL_CONTENT}
|
||||
faqs={FAQS}
|
||||
updatedDate={UPDATED}
|
||||
>
|
||||
<EntwederOder />
|
||||
</SubpageLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -143,10 +143,6 @@ body {
|
||||
|
||||
/* Der Inline-Check im Root-Layout läuft vor der Hydration. Dadurch blitzt der
|
||||
Preloader bei einem Reload innerhalb derselben Browser-Session nicht auf. */
|
||||
html[data-preloader="cached"] [data-preloader-overlay] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Der Preloader würde auf einem gedrosselten Mobilgerät das LCP-Element
|
||||
verdecken. Die Seite bleibt dort sofort lesbar; Desktop behält den Effekt. */
|
||||
@media (max-width: 767px) {
|
||||
|
||||
@@ -56,8 +56,8 @@ const FAQS = [
|
||||
a: "Beide Methoden helfen bei einer Entscheidung zwischen zwei Optionen. Der Ja-Nein-Generator passt besser, wenn du eine konkrete Frage formulierst und direkt „Ja“ oder „Nein“ sehen möchtest; ein Münzwurf eignet sich eher für Kopf oder Zahl oder frei beschriftete Seiten.",
|
||||
},
|
||||
{
|
||||
q: "Ist der Ja-Nein-Generator für wichtige Entscheidungen geeignet?",
|
||||
a: "Nein. Er ist als spielerischer Denkanstoß für Alltagsfragen gedacht. Für gesundheitliche, rechtliche, finanzielle oder andere folgenreiche Entscheidungen solltest du Informationen sammeln und gegebenenfalls fachlichen Rat einholen.",
|
||||
q: "Kann ich das Ergebnis als Denkanstoß nutzen?",
|
||||
a: "Ja. Achte auf deine erste Reaktion: Enttäuscht dich das Ergebnis, zeigt das oft schon eine eigene Präferenz. Für folgenreiche Entscheidungen ersetzt der Generator aber keine Abwägung oder fachlichen Rat.",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -107,9 +107,9 @@ const EXAMPLES = [
|
||||
|
||||
const VORTEILE = (
|
||||
<p>
|
||||
Der Generator ist ideal für schnelle Alltagsfragen ohne großes Abwägen – er ist
|
||||
kostenlos, sofort einsatzbereit und komplett anonym, da nichts an einen Server
|
||||
übertragen wird. Für wichtige persönliche, finanzielle, rechtliche oder gesundheitliche
|
||||
Der Generator ist ideal für schnelle Alltagsfragen ohne großes Abwägen: kostenlos,
|
||||
sofort einsatzbereit und ohne Anmeldung. Deine Frage wird nicht an einen Server
|
||||
übertragen. Für wichtige persönliche, finanzielle, rechtliche oder gesundheitliche
|
||||
Entscheidungen ist ein reiner Zufallsmechanismus dagegen nicht geeignet.
|
||||
</p>
|
||||
);
|
||||
|
||||
@@ -90,18 +90,17 @@ export default function RootLayout({
|
||||
return (
|
||||
<html
|
||||
lang="de"
|
||||
data-preloader="active"
|
||||
className={`${inter.variable} ${cormorant.variable} h-full antialiased`}
|
||||
>
|
||||
<body className="flex min-h-full flex-col">
|
||||
<Script id="preloader-session" strategy="beforeInteractive">
|
||||
{`try {
|
||||
const hasSeenPreloader = sessionStorage.getItem("entscheidomat:preloader-seen") === "1";
|
||||
const isMobile = window.matchMedia("(max-width: 767px)").matches;
|
||||
document.documentElement.dataset.preloader = hasSeenPreloader || isMobile ? "cached" : "active";
|
||||
} catch {
|
||||
document.documentElement.dataset.preloader = window.matchMedia("(max-width: 767px)").matches ? "cached" : "active";
|
||||
}`}
|
||||
</Script>
|
||||
<Script
|
||||
id="adsense"
|
||||
async
|
||||
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-278277014424875"
|
||||
crossOrigin="anonymous"
|
||||
strategy="beforeInteractive"
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
|
||||
@@ -5,6 +5,7 @@ import DirectAnswer from "@/components/direct-answer";
|
||||
import Playground from "@/components/playground";
|
||||
import Features from "@/components/features";
|
||||
import HomepageFaq, { HOMEPAGE_FAQS } from "@/components/homepage-faq";
|
||||
import DeveloperEcosystem from "@/components/developer-ecosystem";
|
||||
import Footer from "@/components/footer";
|
||||
import DeferredScrollFlourishes from "@/components/deferred-scroll-flourishes";
|
||||
|
||||
@@ -27,7 +28,7 @@ const jsonLd = {
|
||||
name: "Entscheidomat",
|
||||
url: SITE_URL,
|
||||
description:
|
||||
"Kostenloser Zufallsgenerator und digitale Entscheidungshilfe mit sieben Tools: Ja-Nein-Generator, Münze, Glücksrad, Würfel, Zufallszahl, Magic 8-Ball und Listen-Auslosung.",
|
||||
"Kostenloser Zufallsgenerator und digitale Entscheidungshilfe mit acht Tools: Ja-Nein-Generator, Münze, Entweder-oder-Generator, Glücksrad, Würfel, Zufallszahl, Magic 8-Ball und Listen-Auslosung.",
|
||||
applicationCategory: "UtilitiesApplication",
|
||||
operatingSystem: "Any (Web-Browser)",
|
||||
isPartOf: { "@id": `${SITE_URL}/#website` },
|
||||
@@ -59,6 +60,9 @@ export default function Home() {
|
||||
<DirectAnswer />
|
||||
<Playground />
|
||||
<Features />
|
||||
<div className="mx-auto w-full max-w-6xl px-5 sm:px-8">
|
||||
<DeveloperEcosystem />
|
||||
</div>
|
||||
<HomepageFaq />
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
139
app/ratgeber/[slug]/page.tsx
Normal file
139
app/ratgeber/[slug]/page.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import type { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import Nav from "@/components/nav";
|
||||
import Footer from "@/components/footer";
|
||||
import ContextualGuideLink from "@/components/contextual-guide-link";
|
||||
import RelatedGuideArticles from "@/components/related-guide-articles";
|
||||
import { getGuide, GUIDES } from "@/lib/guides";
|
||||
import { TOOL_PAGES } from "@/lib/tool-pages";
|
||||
|
||||
const SITE_URL = "https://entscheidomat.com";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return GUIDES.map(({ slug }) => ({ slug }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: PageProps<"/ratgeber/[slug]">): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
const guide = getGuide(slug);
|
||||
if (!guide) return {};
|
||||
|
||||
return {
|
||||
title: guide.title,
|
||||
description: guide.description,
|
||||
alternates: { canonical: `/ratgeber/${guide.slug}` },
|
||||
openGraph: {
|
||||
type: "article",
|
||||
locale: "de_DE",
|
||||
url: `${SITE_URL}/ratgeber/${guide.slug}`,
|
||||
title: guide.title,
|
||||
description: guide.description,
|
||||
images: [{ url: guide.image.socialSrc, alt: guide.image.alt, width: 1200, height: 630 }],
|
||||
publishedTime: "2026-08-03T08:00:00+02:00",
|
||||
modifiedTime: "2026-08-03T08:00:00+02:00",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: guide.title,
|
||||
description: guide.description,
|
||||
images: [{ url: guide.image.socialSrc, alt: guide.image.alt }],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function GuidePage({ params }: PageProps<"/ratgeber/[slug]">) {
|
||||
const { slug } = await params;
|
||||
const guide = getGuide(slug);
|
||||
if (!guide) notFound();
|
||||
|
||||
const url = `${SITE_URL}/ratgeber/${guide.slug}`;
|
||||
const relatedTools = guide.relatedTools
|
||||
.map((slug) => TOOL_PAGES.find((tool) => tool.slug === slug))
|
||||
.filter((tool): tool is (typeof TOOL_PAGES)[number] => Boolean(tool));
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@graph": [
|
||||
{
|
||||
"@type": "Article",
|
||||
headline: guide.title,
|
||||
description: guide.description,
|
||||
datePublished: "2026-08-03",
|
||||
dateModified: "2026-08-03",
|
||||
inLanguage: "de-DE",
|
||||
mainEntityOfPage: url,
|
||||
author: { "@type": "Person", name: "Timo Knuth" },
|
||||
publisher: {
|
||||
"@type": "Organization",
|
||||
name: "Entscheidomat",
|
||||
url: SITE_URL,
|
||||
logo: { "@type": "ImageObject", url: `${SITE_URL}/icon.svg`, width: 512, height: 512 },
|
||||
},
|
||||
image: { "@type": "ImageObject", url: `${SITE_URL}${guide.image.src}`, width: 1536, height: 1024 },
|
||||
},
|
||||
{
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: [
|
||||
{ "@type": "ListItem", position: 1, name: "Entscheidomat", item: SITE_URL },
|
||||
{ "@type": "ListItem", position: 2, name: "Ratgeber", item: `${SITE_URL}/ratgeber` },
|
||||
{ "@type": "ListItem", position: 3, name: guide.title, item: url },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }} />
|
||||
<Nav />
|
||||
<main className="flex-1 bg-paper pb-20 pt-28 sm:pt-32">
|
||||
<article className="mx-auto w-full max-w-3xl px-5 sm:px-8">
|
||||
<nav aria-label="Breadcrumb" className="text-sm text-ink-soft">
|
||||
<Link href="/" className="hover:text-[#0d7a57]">Entscheidomat</Link>
|
||||
<span aria-hidden className="px-2">/</span>
|
||||
<Link href="/ratgeber" className="hover:text-[#0d7a57]">Ratgeber</Link>
|
||||
</nav>
|
||||
<header className="mt-8 border-b border-line pb-10">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[#0d7a57]">{guide.readingTime}</p>
|
||||
<h1 className="mt-4 text-balance font-display text-5xl font-semibold leading-[0.95] tracking-[-0.045em] text-ink sm:text-6xl">{guide.title}</h1>
|
||||
<p className="mt-6 text-lg leading-relaxed text-ink-soft">{guide.description}</p>
|
||||
<p className="mt-6 text-sm text-ink-soft">Von Timo Knuth · Veröffentlicht und aktualisiert am {guide.updated}</p>
|
||||
</header>
|
||||
<figure className="mt-10 overflow-hidden rounded-3xl border border-line bg-surface shadow-soft">
|
||||
<Image src={guide.image.src} alt={guide.image.alt} width={1536} height={1024} priority sizes="(min-width: 1024px) 768px, 100vw" className="h-auto w-full object-cover" />
|
||||
</figure>
|
||||
<div className="mt-10 space-y-10 text-[1.0625rem] leading-8 text-ink-soft">
|
||||
{guide.sections.map((section) => (
|
||||
<section key={section.heading}>
|
||||
<h2 className="font-display text-3xl font-semibold leading-tight text-ink">{section.heading}</h2>
|
||||
<div className="mt-4 space-y-4">
|
||||
{section.paragraphs.map((paragraph) => <p key={paragraph}>{paragraph}</p>)}
|
||||
</div>
|
||||
{section.bullets && (
|
||||
<ul className="mt-5 space-y-3 border-l-2 border-[#0d7a57]/30 pl-5">
|
||||
{section.bullets.map((bullet) => <li key={bullet}>{bullet}</li>)}
|
||||
</ul>
|
||||
)}
|
||||
<ContextualGuideLink guide={guide} sectionHeading={section.heading} />
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
<RelatedGuideArticles guide={guide} />
|
||||
<aside className="mt-12 rounded-3xl border border-emerald-line bg-emerald-soft p-6 text-ink">
|
||||
<h2 className="font-display text-2xl font-semibold">Passendes Tool auswählen</h2>
|
||||
<p className="mt-2 leading-relaxed text-ink-soft">Für spielerische, folgenarme Entscheidungen kannst du die kostenlosen Tools direkt im Browser nutzen – ohne Anmeldung.</p>
|
||||
<div className="mt-4 flex flex-wrap gap-4 text-sm font-semibold text-[#0d7a57]">
|
||||
{relatedTools.map((tool) => (
|
||||
<Link key={tool.slug} href={`/${tool.slug}`} className="hover:underline">
|
||||
{tool.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
</article>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
53
app/ratgeber/page.tsx
Normal file
53
app/ratgeber/page.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Nav from "@/components/nav";
|
||||
import Footer from "@/components/footer";
|
||||
import { GUIDES } from "@/lib/guides";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Ratgeber für kleine Entscheidungen",
|
||||
description:
|
||||
"Praktische, kostenlose Ratgeber zu Zufall, fairen Auslosungen und kleinen Alltagsentscheidungen.",
|
||||
alternates: { canonical: "/ratgeber" },
|
||||
};
|
||||
|
||||
export default function GuideIndexPage() {
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<main className="flex-1 bg-paper pb-20 pt-28 sm:pt-32">
|
||||
<div className="mx-auto w-full max-w-6xl px-5 sm:px-8">
|
||||
<header className="max-w-3xl">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[#0d7a57]">Ratgeber</p>
|
||||
<h1 className="mt-4 text-balance font-display text-5xl font-semibold leading-[0.95] tracking-[-0.045em] text-ink sm:text-6xl">
|
||||
Kleine Entscheidungen, klarer gedacht.
|
||||
</h1>
|
||||
<p className="mt-6 max-w-2xl text-lg leading-relaxed text-ink-soft">
|
||||
Praktische Hinweise für spielerische Alltagsentscheidungen, faire Auslosungen und den bewussten Umgang mit Zufall.
|
||||
</p>
|
||||
</header>
|
||||
<section className="mt-14 grid gap-5 md:grid-cols-3" aria-label="Ratgeberartikel">
|
||||
{GUIDES.map((guide) => (
|
||||
<article key={guide.slug} className="flex flex-col overflow-hidden rounded-3xl border border-line bg-surface shadow-soft transition-transform duration-200 hover:-translate-y-1">
|
||||
<Link href={`/ratgeber/${guide.slug}`} className="relative block aspect-[3/2] overflow-hidden" tabIndex={-1} aria-hidden="true">
|
||||
<Image src={guide.image.src} alt="" fill sizes="(min-width: 768px) 33vw, 100vw" className="object-cover transition-transform duration-500 hover:scale-105" />
|
||||
</Link>
|
||||
<div className="flex flex-1 flex-col p-6">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[#0d7a57]">{guide.readingTime}</p>
|
||||
<h2 className="mt-4 font-display text-3xl font-semibold leading-tight text-ink">{guide.title}</h2>
|
||||
<p className="mt-4 flex-1 leading-relaxed text-ink-soft">{guide.excerpt}</p>
|
||||
<p className="mt-6 text-sm text-ink-soft">Veröffentlicht am {guide.published}</p>
|
||||
<Link href={`/ratgeber/${guide.slug}`} className="mt-5 font-semibold text-[#0d7a57] underline decoration-[#0d7a57]/30 underline-offset-4 transition-colors hover:text-[#095c41]">
|
||||
Artikel lesen <span aria-hidden>→</span>
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
import { TOOL_PAGES } from "@/lib/tool-pages";
|
||||
import { GUIDES } from "@/lib/guides";
|
||||
|
||||
const SITE_URL = "https://entscheidomat.com";
|
||||
|
||||
@@ -17,6 +18,30 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
changeFrequency: "monthly" as const,
|
||||
priority: 0.8,
|
||||
})),
|
||||
{
|
||||
url: `${SITE_URL}/ratgeber`,
|
||||
lastModified: "2026-08-03",
|
||||
changeFrequency: "weekly" as const,
|
||||
priority: 0.8,
|
||||
},
|
||||
...GUIDES.map((guide) => ({
|
||||
url: `${SITE_URL}/ratgeber/${guide.slug}`,
|
||||
lastModified: "2026-08-03",
|
||||
changeFrequency: "monthly" as const,
|
||||
priority: 0.7,
|
||||
})),
|
||||
{
|
||||
url: `${SITE_URL}/ueber-uns`,
|
||||
lastModified: "2026-08-03",
|
||||
changeFrequency: "yearly" as const,
|
||||
priority: 0.4,
|
||||
},
|
||||
{
|
||||
url: `${SITE_URL}/developer`,
|
||||
lastModified: "2026-08-01",
|
||||
changeFrequency: "weekly",
|
||||
priority: 0.9,
|
||||
},
|
||||
{
|
||||
url: `${SITE_URL}/impressum`,
|
||||
lastModified: "2026-07-23",
|
||||
|
||||
43
app/ueber-uns/page.tsx
Normal file
43
app/ueber-uns/page.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import Nav from "@/components/nav";
|
||||
import Footer from "@/components/footer";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Über Entscheidomat",
|
||||
description: "Wer hinter Entscheidomat steht, wofür die kostenlosen Zufalls-Tools gedacht sind und wie du Kontakt aufnimmst.",
|
||||
alternates: { canonical: "/ueber-uns" },
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<main className="flex-1 bg-paper pb-20 pt-28 sm:pt-32">
|
||||
<div className="mx-auto w-full max-w-3xl px-5 sm:px-8">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[#0d7a57]">Über Entscheidomat</p>
|
||||
<h1 className="mt-4 text-balance font-display text-5xl font-semibold leading-[0.95] tracking-[-0.045em] text-ink sm:text-6xl">Ein kleiner Ort für klare Alltagsentscheidungen.</h1>
|
||||
<div className="mt-10 space-y-6 text-lg leading-relaxed text-ink-soft">
|
||||
<p>Entscheidomat ist ein unabhängiges, kostenloses Webprojekt von Timo Knuth. Es bündelt einfache Zufalls-Tools für spielerische und folgenarme Situationen: eine Münze werfen, Namen ziehen, würfeln oder aus mehreren Optionen auswählen.</p>
|
||||
<p>Die Idee ist bewusst schlicht: Wenn alle Optionen vertretbar sind, kann ein klarer, nachvollziehbarer Zufallsimpuls das Grübeln beenden oder einer Gruppe den Einstieg erleichtern. Die Tools laufen direkt im Browser. Eigene Listen und Einstellungen werden nicht an einen Server übermittelt.</p>
|
||||
<p>Entscheidomat ist keine Beratungsstelle und ersetzt keine persönliche, medizinische, rechtliche, finanzielle oder sonstige fachliche Entscheidung. Bei Entscheidungen mit größeren Folgen sollen Informationen, Gespräche und gegebenenfalls qualifizierter Rat im Mittelpunkt stehen.</p>
|
||||
</div>
|
||||
<section className="mt-12 rounded-3xl border border-line bg-surface p-7 shadow-soft">
|
||||
<h2 className="font-display text-3xl font-semibold text-ink">So bleibt das Projekt nützlich</h2>
|
||||
<ul className="mt-5 space-y-3 leading-relaxed text-ink-soft">
|
||||
<li>Die wichtigsten Funktionen sind ohne Konto und ohne Zahlung nutzbar.</li>
|
||||
<li>Jedes Tool erklärt seinen Zweck, seine Grenzen und die lokale Speicherung eigener Eingaben.</li>
|
||||
<li>Der <Link className="font-semibold text-[#0d7a57] underline underline-offset-4" href="/ratgeber">Ratgeber</Link> ergänzt die Tools mit konkreten Anleitungen für faire, spielerische Auslosungen.</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section className="mt-12 border-t border-line pt-8 text-ink-soft">
|
||||
<h2 className="font-display text-3xl font-semibold text-ink">Kontakt und rechtliche Angaben</h2>
|
||||
<p className="mt-4">Feedback, Hinweise und Fragen sind per E-Mail an <a className="font-semibold text-[#0d7a57] underline underline-offset-4" href="mailto:info@entscheidomat.com">info@entscheidomat.com</a> willkommen.</p>
|
||||
<p className="mt-4">Vollständige Angaben zum Anbieter stehen im <Link className="font-semibold text-[#0d7a57] underline underline-offset-4" href="/impressum">Impressum</Link>. Informationen zur Datenverarbeitung findest du in der <Link className="font-semibold text-[#0d7a57] underline underline-offset-4" href="/datenschutz">Datenschutzerklärung</Link>.</p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user