> For the complete documentation index, see [llms.txt](https://unsloth.ai/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://unsloth.ai/docs/de/modelle/glm-5.2.md).

# GLM-5.2 - So wird es lokal ausgeführt

GLM-5.2 ist das neue offene Modell von Z.ai und liefert SOTA-Leistung bei langfristigem Coding, Reasoning und agentischen Aufgaben. Mit **744B Parametern**, 40B aktiven Parametern und einem **1M-Kontext** -Fenster kann es jetzt lokal ausgeführt werden mit [Unsloth Dynamic](/docs/de/grundlagen/unsloth-dynamic-2.0-ggufs.md) GGUFs. GLM-5.2 ist das **stärkste offene Modell** bis heute und liegt bei Artificial Analysis und vielen anderen Benchmarks auf Augenhöhe mit Claude 4.8 Opus, GPT-5.5 und Gemini 3.1 Pro.

[Dynamic 1-Bit erreicht **\~76.2%**](/docs/de/modelle/glm-5.2.md#quantization-analysis) Top-1-Genauigkeit und ist dabei **86 % kleiner**. Dynamic 2-Bit erreicht **\~82%** Genauigkeit und ist dabei **84 % kleiner**. Das bedeutet nicht, dass das Modell 82 % schlechter ist, nur weil es 84 % kleiner ist – es ist vielmehr nur etwa 18 % weniger genau als das volle **1,5 TB** Modell. Vielen Dank an Z.ai für den Day-Zero-Zugriff für Unsloth. [**GLM-5.2-GGUF**](https://huggingface.co/unsloth/GLM-5.2-GGUF)

<a href="/pages/58e5f3df7be61773c8d7055c30a65310240079a4#run-glm-5.2-tutorials" class="button primary">GLM-5.2-Tutorials ausführen</a><a href="/pages/58e5f3df7be61773c8d7055c30a65310240079a4#quantization-analysis" class="button secondary">Quantisierungsergebnisse</a>

#### &#x20;:gear: Nutzungsanleitung

{% columns %}
{% column %}
Die dynamische 2-Bit-Quantisierung `UD-IQ2_M` verwendet **239 GB** an Speicherplatz auf der Festplatte – das passt direkt auf einen **Mac mit 256 GB Unified Memory** und funktioniert gut auf einer **1x24-GB-GPU** und **256 GB RAM** mit MoE-Offloading.

Die **1-Bit** Quantisierung passt auf 223 GB RAM und 8-Bit erfordert 810 GB RAM. Sehen Sie den 1-Bit-GGUF rechts in Aktion:
{% endcolumn %}

{% column %}

<figure><img src="/files/2f87c4a9b411b9419067e7ad90d4e7b4426f3bf9" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

**Tabelle: Hardwareanforderungen für die Inferenz** (Einheiten = Gesamtspeicher: RAM + VRAM oder Unified Memory)

<table><thead><tr><th>1-Bit</th><th>2-Bit</th><th width="128">3-Bit</th><th>4-Bit</th><th>5-Bit</th><th>8-Bit</th></tr></thead><tbody><tr><td>223 GB</td><td>245 GB</td><td>290–360 GB</td><td>372–475 GB</td><td>570 GB</td><td>810 GB</td></tr></tbody></table>

{% hint style="success" %}
Für die beste Leistung sollten Sie sicherstellen, dass Ihr insgesamt verfügbarer Speicher, einschließlich VRAM und Systemspeicher, die Größe der quantisierten Modelldatei mit einem komfortablen Puffer übersteigt.
{% endhint %}

### Empfohlene Einstellungen

GLM-5.2 hat **3 Denkmodi**. Nicht-Denken und Denken in zwei Modi: **High** + **Max**. Verwenden Sie Max Thinking für komplizierte Aufgaben. In [Unsloth Studio](#run-glm-5.2-in-unsloth-studio) können Sie High + Max Thinking und Nicht-Thinking ganz einfach über eine UI umschalten.

Verwenden Sie diese Einstellungen für die meisten Anwendungsfälle:

| Standardeinstellungen (die meisten Aufgaben) | SWE-Bench Pro      |
| -------------------------------------------- | ------------------ |
| `Temperatur` = 1.0                           | `Temperatur` = 1.0 |
| `top_p` = 0.95                               | `top_p` = 1.0      |

* **Maximales Kontextfenster:** `1,048,576`.

#### Thinking deaktivieren, den Reasoning-Aufwand ändern

GLM 5.2 verwendet standardmäßig Reasoning. Es unterstützt außerdem Reasoning-Aufwände, bei denen `reasoning_effort` "high", "max" oder deaktiviert sein kann.

Um Thinking zu deaktivieren, verwenden Sie `--chat-template-kwargs '{"enable_thinking":false}'`. Wenn Sie auf **Windows** PowerShell verwenden, nutzen Sie: `--chat-template-kwargs "{\"enable_thinking\":false}"`

Sie können auch `--reasoning on` oder `--reasoning off` auch jetzt in llama.cpp verwenden!

Zur Anpassung des Reasoning-Aufwands und/oder zum Deaktivieren des Reasonings verwenden Sie die folgenden Beispiele:

{% code overflow="wrap" expandable="true" %}

```bash
--chat-template-kwargs '{"reasoning_effort":"max"}'
--chat-template-kwargs '{"reasoning_effort":"high"}'
--chat-template-kwargs '{"enable_thinking":false}'
```

{% endcode %}

### 📈 Quantisierungsanalyse

Wir haben außerdem KLD-(KL-Divergenz-)Benchmarks ausgeführt, um die Genauigkeit unserer Quantisierungen von GLM-5.2-GGUF zu bewerten. Dynamische 4-Bit-UD-Q4\_K\_XL und dynamische 5-Bit-UD-Q5\_K\_XL sind größtenteils verlustfrei, und kleinere Quantisierungen funktionieren ebenfalls hervorragend, indem wichtige Schichten dynamisch in höherer Präzision belassen werden und unwichtige in niedrigen Bits.

Bei reiner Top-1-%-Genauigkeit, **erreicht die dynamische 1-Bit-Variante etwa 76,2 % Genauigkeit und ist dabei 86 % kleiner**! Dynamische 2-Bit erreicht etwa 82 % Genauigkeit und ist dabei 84 % kleiner. Dies zeigt, dass das dynamische Quantisieren einiger Schichten auf höhere Präzision das Modell nicht 86 % schlechter macht, obwohl es 86 % kleiner ist – sondern nur 24 % weniger leistungsfähig als das volle 1,5-TB-Modell.

**Aber was beschreibt „76 % Genauigkeit“ eigentlich?**

76 % Top-1-% bedeutet NICHT, dass aus „Die Hauptstadt von Frankreich ist“ zu 76 % Paris und zu 24 % Sydney gewählt wird. Das Modell ist dadurch NICHT um 24 % „dümmer“. Dafür gilt Paris immer zu 100 %, und Sydney zu 0 %. Die 76-%-Zahl enthält Füllwörter und Stoppwörter über den gesamten Korpus hinweg, zum Beispiel bei der Frage:

„Einen Roman erstellen“ führt aufgrund des LLM-Samplings zu:

* Ich werde jetzt einen Roman erstellen...
* Der Roman steht unten:
* Welches Genre soll es sein?

Jedes Beispiel ist korrekt, aber die Verteilung \[I, The, What] ist das, was sich ändert – die Baseline könnte \[I] 100 % der Zeit verwenden, aber jetzt ist \[I] 76 % und \[The] 24 %.

Es **NICHT** bedeutet, dass Sie 24 % der Zeit falsche Ausgaben wie Kauderwelsch oder andere falsche Ausgaben erhalten.

<figure><img src="/files/6989f924fa603d199b3e9e95314e1e427a29995e" alt=""><figcaption></figcaption></figure>

99,9 % KLD ist ebenfalls im Allgemeinen gut – ab 4-Bit gibt es jedoch einen größeren Zugewinn, daher ist für massive Out-of-Distribution-Aufgaben dynamisches 4-Bit wahrscheinlich am besten.

Top-1-% ist eine „erzwungene“ Binomialverteilung des KLD selbst. KLD ist der „Abstand“ zwischen den Wahrscheinlichkeiten der Baseline (BF16 oder Q8\_0) und der quantisierten Version. Das Ziel der Quantisierung ist es, die folgende Zielfunktion zu minimieren:

$$
\text{minimize    } \frac{1}{n} \sum{\text{D}\_{\text{KL}}\[\text{ }f(q(W))\text{ }||\text{ } f(W))\text{ }]}
$$

Wobei `f` der Forward-Pass des Sprachmodells ist und `q` die Quantisierungsoperation ist und `W` die Parameter oder Gewichte des Modells sind. Ziel ist es, den „Abstand“ zwischen den Logits-Ausgaben der Baseline `f(W)` und der Ausgabe des quantisierten Modells so klein wie möglich zu machen. Wenn Sie 0 KLD erreichen, haben Sie das Modell perfekt rekonstruiert!

<figure><img src="/files/310f39e90326826b10b05db9daff3d86ce74fdfa" alt=""><figcaption></figcaption></figure>

Wir verwenden **den mittleren KLD** wie unten, da es teuer ist, KLD über das gesamte Trainingskorpus auszuführen (zum Beispiel 15 T Token) – stattdessen machen wir **Sampling,** und erhalten eine kleine repräsentative Teilmenge des Trainingskorpus **/** für die Downstream-Aufgabe und optimieren darauf. Der mittlere KLD folgt im Allgemeinen einem monotonen Trend gegenüber dem Speicherplatz und zeigt, dass GLM 5.2 selbst bei 1-Bit gut funktioniert!

Top-1-%-Genauigkeit ist einfach ein Greedy-Decoding-Operator, bei dem wir annehmen, dass das Argmax-Element ausgewählt wird, und bei 1-Bit wählt es zu 76 % dasselbe wie das Argmax der Baseline.

<figure><img src="/files/8095e87733a3f0827f7c4fcc042fd21396392ac9" alt=""><figcaption></figcaption></figure>

## Anleitungen zum Ausführen von GLM-5.2:

Sie können GLM-5.2 jetzt in [llama.cpp](#run-in-llama.cpp) und [Unsloth Studio](https://unsloth.ai/docs/de/modelle/pages/58e5f3df7be61773c8d7055c30a65310240079a4#run-glm-5.2-in-unsloth-studio). Wir werden die 239-GB [`UD-IQ2_M`](https://huggingface.co/unsloth/GLM-5.2-GGUF/tree/main/UD-IQ2_M) Quantisierung nutzen, um hinsichtlich Zugänglichkeit und Genauigkeit die besten Ergebnisse zu erzielen.

### 🦥 GLM-5.2 in Unsloth Studio ausführen

GLM-5.2 kann in [Unsloth Studio](/docs/de/neu/studio.md), einer Open-Source-Web-UI für lokale KI. **Unsloth Studio lagert automatisch in den RAM aus und erkennt Multi-GPU-Setups**. Mit Unsloth Studio können Sie Modelle lokal auf **MacOS, Windows**, Linux und:

{% columns %}
{% column %}

* Suchen, herunterladen, [GGUFs ausführen](/docs/de/neu/studio.md#run-models-locally) und Safetensor-Modelle
* [**Selbstheilend** Tool-Aufruf](/docs/de/neu/studio.md#execute-code--heal-tool-calling) + **Websuche**
* [**Code-Ausführung**](/docs/de/neu/studio.md#run-models-locally) (Python, Bash)
* [Automatische Inferenz](/docs/de/neu/studio.md#model-arena) Parameter-Tuning (Temp, Top-p usw.)
* Schnelle CPU- + GPU-Inferenz via llama.cpp
* [LLMs trainieren](/docs/de/neu/studio.md#no-code-training) 2x schneller mit 70 % weniger VRAM
  {% endcolumn %}

{% column %}

<figure><img src="/files/ed4920444998dc0af3913c66da4c70c6ee5326f0" alt=""><figcaption><p>2-Bit-GLM-5.2-GGUF-Ausgabe</p></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

{% stepper %}
{% step %}
**Unsloth installieren und starten**

Zur Installation führen Sie in Ihrem Terminal Folgendes aus:

MacOS, Linux, WSL:

```bash
curl -fsSL https://unsloth.ai/install.sh | sh
```

Windows PowerShell:

```bash
irm https://unsloth.ai/install.ps1 | iex
```

**Unsloth starten**

MacOS, Linux, WSL und Windows:

```bash
unsloth studio -H 0.0.0.0 -p 8888
```

Dann öffnen Sie `http://127.0.0.1:8888` (oder Ihre spezifische URL) in Ihrem Browser.

**Unsloth sicher mit HTTPS und Cloudflare starten**

**NEU!** Unsloth bietet jetzt eine sichere Möglichkeit, Studio über HTTPS über einen kostenlosen Cloudflare-Tunnel zu starten. Verwenden Sie das Folgende (funktioniert unter Windows, Mac und Linux):

```bash
unsloth studio --secure
```

{% endstep %}

{% step %}
**GLM-5.2 suchen und herunterladen**

Unsloth Studio lagert automatisch in den RAM aus und erkennt Multi-GPU-Setups. Beim ersten Start müssen Sie ein Passwort erstellen, um Ihr Konto zu sichern, und sich später erneut anmelden.

Dann gehen Sie zum [Unsloth Chat](/docs/de/neu/studio/chat.md) Tab und suchen Sie nach **GLM-5.2** in der Suchleiste und laden Sie Ihr gewünschtes Modell und die gewünschte Quantisierung herunter. Stellen Sie sicher, dass Sie genügend Rechenleistung haben, um das Modell auszuführen.

<div data-with-frame="true"><figure><img src="/files/3dee9cd8ffd749ad9d1629a3e9f32ffb02464571" alt="" width="563"><figcaption></figcaption></figure></div>
{% endstep %}

{% step %}
**GLM-5.2 ausführen**

Die Inferenzparameter sollten bei der Verwendung von Unsloth Studio automatisch gesetzt werden; Sie können sie jedoch weiterhin manuell ändern. Sie können auch die Kontextlänge, die Chat-Vorlage und andere Einstellungen bearbeiten.

Weitere Informationen finden Sie in unserem [Inferenzleitfaden für Unsloth Studio](/docs/de/neu/studio/chat.md).

<figure><img src="/files/09bc2ab6a892e6f635e2ed7a996d77e9ace81cdc" alt=""><figcaption><p>Beispiel für 1-Bit-GLM-5.2, das mit Canvas ausgeführt wird</p></figcaption></figure>
{% endstep %}
{% endstepper %}

### 🦙 GLM-5.2 in llama.cpp ausführen

Für diese Anleitung werden wir die `UD-IQ2_M` Quantisierung ausführen, die mindestens 245 GB RAM erfordert. Sie können den Quantisierungstyp gerne ändern. Für diese Anleitungen werden wir [llama.cpp](llama.cpphttps://github.com/ggml-org/llama.cpp) für schnelle lokale Inferenz verwenden. GGUF: [**GLM-5.2-GGUF**](https://huggingface.co/unsloth/GLM-5.2-GGUF)&#x20;

{% stepper %}
{% step %}
Besorgen Sie sich die neueste `llama.cpp` **auf** [**hier auf GitHub**](https://github.com/ggml-org/llama.cpp). Sie können auch den untenstehenden Build-Anweisungen folgen. Ändern Sie `-DGGML_CUDA=ON` zu `-DGGML_CUDA=OFF` wenn Sie keine GPU haben oder nur CPU-Inferenz möchten. **Für Apple-Mac-/Metal-Geräte**, setzen Sie `-DGGML_CUDA=OFF` und fahren Sie dann wie gewohnt fort – Metal-Unterstützung ist standardmäßig aktiviert.

```bash
apt-get update
apt-get install pciutils build-essential cmake curl libcurl4-openssl-dev -y
git clone https://github.com/ggml-org/llama.cpp
cmake llama.cpp -B llama.cpp/build \
    -DBUILD_SHARED_LIBS=OFF -DGGML_CUDA=ON
cmake --build llama.cpp/build --config Release -j --clean-first --target llama-cli llama-mtmd-cli llama-server llama-gguf-split
cp llama.cpp/build/bin/llama-* llama.cpp
```

{% endstep %}

{% step %}
Sie können jetzt `llama.cpp` direkt verwenden, um Modelle zu laden und herunterzuladen, genau wie `ollama run`. Wählen Sie zuerst den gewünschten Quantisierungstyp aus, z. B. `UD-IQ2_M`. Verwenden Sie außerdem `export LLAMA_CACHE="unsloth/GLM-5.2-GGUF"` um zu erzwingen, dass `llama.cpp` an einem bestimmten Speicherort gespeichert wird. **Beachten Sie, dass dieser Downloadvorgang sehr langsam sein kann**, daher ist es wahrscheinlich am besten, den manuellen Download-Prozess im nächsten Abschnitt zu verwenden.

```bash
export LLAMA_CACHE="unsloth/GLM-5.2-GGUF"
./llama.cpp/llama-cli \
    -hf unsloth/GLM-5.2-GGUF:UD-IQ2_M \
    --temp 1.0 \
    --top-p 0.95 \
    --min-p 0.01
```

{% endstep %}

{% step %}
Wenn Sie das Modell manuell herunterladen möchten **(viel schneller!)**, können wir das Modell über den untenstehenden Code herunterladen (nach der Installation von `pip install huggingface_hub`). Wenn Downloads hängen bleiben, siehe: [Hugging Face Hub, XET-Debugging](/docs/de/grundlagen/troubleshooting-and-faqs/hugging-face-hub-xet-debugging.md)

```bash
hf download unsloth/GLM-5.2-GGUF \
    --local-dir unsloth/GLM-5.2-GGUF \
    --include "*UD-IQ2_M*" # Verwenden Sie "*UD-Q8_K_XL*" für nahezu vollständige Präzision
```

Wenn Sie die dynamische 1-Bit-Version verwenden möchten, dann tun Sie Folgendes:

{% code overflow="wrap" expandable="true" %}

```bash
hf download unsloth/GLM-5.2-GGUF \
    --local-dir unsloth/GLM-5.2-GGUF \
    --include "*UD-IQ1_S*"
```

{% endcode %}
{% endstep %}

{% step %}
Dann führen Sie das Modell im Konversationsmodus aus. Verwenden Sie `unsloth/GLM-5.2-GGUF/UD-IQ2_M/GLM-5.2-UD-IQ2_M-00001-of-00006.gguf` für 2-Bit oder `unsloth/GLM-5.2-GGUF/UD-IQ1_S/GLM-5.2-UD-IQ1_S-00001-of-00006.gguf`  für 1-Bit.

{% code overflow="wrap" %}

```bash
./llama.cpp/llama-cli \
    --model unsloth/GLM-5.2-GGUF/UD-IQ2_M/GLM-5.2-UD-IQ2_M-00001-of-00006.gguf \
    --temp 1.0 \
    --top-p 0.95 \
    --min-p 0.01
```

{% endcode %}
{% endstep %}

{% step %}
Wenn Sie llama-cli starten, sehen Sie:

<figure><img src="/files/eb84e39760b4e8eced1c651c0933d7d890e3a5c3" alt=""><figcaption></figcaption></figure>

Nachdem wir es dann aufgefordert haben, ein kurzes Flappy-Bird-Spiel zu erstellen, erhalten wir:

<figure><img src="/files/905a8439ec50f8caed2dcf495f3b21a8bd77f647" alt=""><figcaption></figcaption></figure>

Mit dem vollständigen Gespräch und dem Spiel unten:

<details>

<summary>Vollständiges Spiel in HTML</summary>

{% code overflow="wrap" expandable="true" %}

```html
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Sonnenuntergangs-Flieger</title>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Fred:wght@400;700&display=swap" rel="stylesheet">
<style>
  :root {
    --sunset-1: #ff6b6b;
    --sunset-2: #feca50;
    --sunset-3: #ff9ff3;
    --dusk: #36306b;
    --night: #1a1746;
    --accent: #ffd93b;
    --coral: #ff6b6b;
    --pipe: #4a902b;
    --pipe-dark: #2d5a1a;
  }

  * { margin: 0; padding: 0; box-sizing: border-box; }

  html, body {
    height: 100%;
    width: 100%;
    overflow: hidden;
    background: var(--night);
    font-family: 'Fred', sans-serif;
    -webkit-user-select: none;
    user-select: none;
    touch-action: manipulation;
  }

  #game-wrap {
    position: relative;
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background: linear-gradient(180deg, #1a1746 0%, #36306b 40%, #ff6b6b 70%, #feca50 100%);
  }

  #game-frame {
    position: relative;
    width: min(100vw, 480px);
    height: min(100vh, 720px);
    max-height: 100vh;
    box-shadow: 0 30px 80px rgba(0,0,0,0.6), inset 0 0 0 1px rgba(255,255,255,0.05);
    overflow: hidden;
    background: linear-gradient(180deg, #4a3a8e 0%, #ff6b6b 60%, #feca50 100%);
  }

  canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
  }

  .overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    pointer-events: none;
    z-index: 10;
    transition: opacity 0.3s ease;
  }

  .overlay.hidden {
    opacity: 0;
    pointer-events: none;
  }

  .overlay.visible {
    opacity: 1;
    pointer-events: auto;
  }

  .panel {
    background: rgba(26, 23, 70, 0.85);
    border: 3px solid var(--accent);
    border-radius: 16px;
    padding: 28px 36px;
    text-align: center;
    color: #fff;
    box-shadow: 0 12px 0 rgba(0,0,0,0.3), 0 0 40px rgba(255, 217, 59, 0.4);
    backdrop-filter: blur(4px);
    transform: translateY(0);
    animation: bob 3s ease-in-out infinite;
  }

  @keyframes bob {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-6px); }
  }

  .title {
    font-family: 'Press Start 2P', monospace;
    font-size: 26px;
    color: var(--accent);
    text-shadow: 3px 3px 0 #b87c0a, 6px 6px 0 rgba(0,0,0,0.3);
    letter-spacing: 1px;
    margin-bottom: 6px;
    line-height: 1.3;
  }

  .subtitle {
    font-size: 14px;
    color: #ffe8a8;
    margin-bottom: 20px;
    font-weight: 700;
  }

  .tap-icon {
    font-size: 42px;
    margin: 8px 0;
    animation: tap 1.2s ease-in-out infinite;
  }

  @keyframes tap {
    0%, 50% { transform: translateY(0) scale(1); }
    20% { transform: translateY(-8px) scale(1.1); }
  }

  .instructions {
    font-size: 13px;
    color: #fff;
    opacity: 0.85;
    margin-top: 10px;
    font-weight: 400;
  }

  .score-row {
    display: flex;
    gap: 24px;
    justify-content: center;
    margin: 12px 0 20px;
  }

  .score-box {
    background: rgba(0,0,0,0.4);
    border: 2px solid var(--accent);
    border-radius: 10px;
    padding: 10px 18px;
    min-width: 80px;
  }

  .score-box .label {
    font-family: 'Press Start 2p', monospace;
    font-size: 9px;
    color: var(--accent);
    margin-bottom: 4px;
    letter-spacing: 1px;
  }

  .score-box .value {
    font-family: 'Press Start 2p', monospace;
    font-size: 18px;
    color: #fff;
  }

  .score-box.best .value { color: var(--coral); }

  .btn {
    font-family: 'Press Start 2p', monospace;
    font-size: 12px;
    color: var(--night);
    background: var(--accent);
    border: none;
    padding: 12px 22px;
    border-radius: 8px;
    cursor: pointer;
    letter-spacing: 1px;
    box-shadow: 0 6px 0 #b87c0a, 0 8px 12px rgba(0,0,0,0.3);
    transition: transform 0.1s, box-shadow 0.1s;
    pointer-events: auto;
  }

  .btn:hover { transform: translateY(2px); box-shadow: 0 4px 0 #b87c0a, 0 6px 10px rgba(0,0,0,0.3); }
  .btn:active { transform: translateY(6px); box-shadow: 0 0 0 #b87c0a, 0 2px 6px rgba(0,0,0,0.3); }

  #hud {
    position: absolute;
    top: 24px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 5;
    font-family: 'Press Start 2P', monospace;
    font-size: 36px;
    color: #fff;
    text-shadow: 3px 3px 0 #b87c0a, 5px 5px 0 rgba(0,0,0,0.5);
    pointer-events: none;
    transition: opacity 0.3s;
    opacity: 0;
  }

  #hud.visible { opacity: 1; }

  #hud .new-best {
    font-size: 11px;
    color: var(--coral);
    text-shadow: 2px 2px 0 #000;
    margin-top: 8px;
    opacity: 0;
    transition: opacity 0.3s;
  }

  #hud.has-new-best .new-best { opacity: 1; animation: pulse 0.6s ease infinite alternate; }

  @keyframes pulse {
    from { transform: translateX(-50%) scale(1); }
    to { transform: translateX(-50%) scale(1.15); }
  }

  .medal {
    font-family: 'Press Start 2P', monospace;
    font-size: 48px;
    margin: 10px 0;
    text-shadow: 3px 3px 0 #000;
  }

  .flash {
    position: absolute;
    inset: 0;
    background: #fff;
    opacity: 0;
    pointer-events: none;
    z-index: 8;
  }

  .footer {
    position: absolute;
    bottom: 12px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 11px;
    color: rgba(255,255,255,0.7);
    font-weight: 400;
    text-align: center;
    pointer-events: none;
    z-index: 9;
  }
</style>
</head>
<body>
<div id="game-wrap">
  <div id="game-frame">
    <canvas id="canvas"></canvas>
    <div id="hud" class="hud">
      <div id="hud-score">0</div>
      <div class="new-best">NEUER BESTWERT!</div>
    </div>
    <div id="flash" class="flash"></div>

    <!-- Startbildschirm -->
    <div id="start-screen" class="overlay visible">
      <div class="panel">
        <div class="title">SONNENUNTERGANG<br>FLIEGER</div>
        <div class="subtitle">— Dämmerungsflug —</div>
        <div class="tap-icon">✊</div>
        <div class="instructions">TIPPEN / LEERTASTE / KLICKEN zum Flattern</div>
      </div>
    </div>

    <!-- Bildschirm nach Spielende -->
    <div id="end-screen" class="overlay hidden">
      <div class="panel">
        <div class="title" style="font-size:22px;color:var(--coral);">SPIEL VORBEI</div>
        <div id="medal" class="medal" style="display:none;">★</div>
        <div class="score-row">
          <div class="score-box">
            <div class="label">PUNKTE</div>
            <div class="value" id="end-score">0</div>
          </div>
          <div class="score-box best">
            <div class="label">BESTWERT</div>
            <div class="value" id="end-best">0</div>
          </div>
        </div>
        <button class="btn" id="restart-btn">NOCHMAL</button>
      </div>
    </div>

    <div class="footer">Ton an • Tippen zum Fliegen</div>
  </div>
</div>

<script>
(() => {
  // ============ Einrichtung ============
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  const frame = document.getElementById('game-frame');
  const hud = document.getElementById('hud');
  const hudScore = document.getElementById('hud-score');
  const startScreen = document.getElementById('start-screen');
  const endScreen = document.getElementById('end-screen');
  const endScore = document.getElementById('end-score');
  const endBest = document.getElementById('end-best');
  const medalEl = document.getElementById('medal');
  const flashEl = document.getElementById('flash');
  const restartBtn = document.getElementById('restart-btn');

  let W = 480, H = 720;
  const dpr = window.devicePixelRatio || 1;

  function resize() {
    const rect = frame.getBoundingClientRect();
    W = rect.width;
    H = rect.height;
    canvas.width = W * dpr;
    canvas.height = H * dpr;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }
  window.addEventListener('resize', resize);
  resize();

  // ============ Audio (Web Audio API) ============
  let audioCtx = null;
  function getAudio() {
    if (!audioCtx) {
      try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); }
      catch(e) { audioCtx = null; }
    }
    if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
    return audioCtx;
  }

  function beep(freq, dur=0.1, type='square', vol=0.1, slideTo=null) {
    const ac = getAudio();
    if (!ac) return;
    const osc = ac.createOscillator();
    const gain = ac.createGain();
    osc.type = type;
    osc.frequency.value = freq;
    if (slideTo) osc.frequency.linearRampToValueAtTime(slideTo, ac.currentTime + dur);
    gain.gain.value = vol;
    gain.gain.exponentialRampToValueAtTime(0.001, ac.currentTime + dur);
    osc.connect(gain);
    gain.connect(ac.destination);
    osc.start();
    osc.stop(ac.currentTime + dur);
  }

  const sfx = {
    flap: () => beep(420, 0.12, 'square', 0.08, 720),
    score: () => { beep(880, 0.08, 'triangle', 0.12); setTimeout(()=>beep(1320, 0.1, 'triangle', 0.1), 60); },
    hit: () => { beep(180, 0.25, 'sawtooth', 0.15, 60); },
    die: () => { beep(120, 0.5, 'sawtooth', 0.15, 40); }
  };

  // ============ Spielstatus ============
  const STATE = { READY: 0, PLAYING: 1, DYING: 2, OVER: 3 };
  let state = STATE.READY;

  let bird, pipes, particles, score, best, frameCount, shake, groundX, clouds, stars;

  best = parseInt(localStorage.getItem('sunsetFlierBest') || '0', 10) || 0;
  endBest.textContent = best;

  // ============ Vogel ============
  function makeBird() {
    return {
      x: W * 0.32,
      y: H * 0.5,
      vy: 0,
      radius: 14,
      wingPhase: 0,
      rotation: 0,
      alive: true
    };
  }

  // ============ Rohre ============
  const PIPE_W = 68;
  const PIPE_GAP = 180;
  const PIPE_SPEED = 2.6;
  const PIPE_SPACING = 220;

  function makePipe(x) {
    const minY = 90;
    const maxY = H - 90 - PIPE_GAP;
    const gapY = minY + Math.random() * (maxY - minY);
    return { x, gapY, scored: false, passed: false };
  }

  function initPipes() {
    pipes = [];
    let x = W + 80;
    for (let i = 0; i < 4; i++) {
      pipes.push(makePipe(x));
      x += PIPE_SPACING;
    }
  }

  // ============ Partikel ============
  function spawnParticles(x, y, color, count=10, vel=3) {
    for (let i = 0; i < count; i++) {
      const angle = Math.random() * Math.PI * 2;
      const speed = (Math.random() * 0.6 + 0.4) * vel;
      particles.push({
        x, y,
        vx: Math.cos(angle) * speed,
        vy: Math.sin(angle) * speed - 1,
        life: 1,
        size: Math.random() * 4 + 2,
        color
      });
    }
  }

  // ============ Hintergrund ============
  function initBg() {
    clouds = [];
    for (let i = 0; i < 4; i++) {
      clouds.push({
        x: Math.random() * W,
        y: 80 + Math.random() * 180,
        scale: 0.6 + Math.random() * 0.8,
        speed: 0.2 + Math.random() * 0.3
      });
    }
    stars = [];
    for (let i = 0; i < 30; i++) {
      stars.push({
        x: Math.random() * W,
        y: Math.random() * H * 0.4,
        r: Math.random() * 1.5 + 0.3,
        tw: Math.random() * Math.PI * 2
      });
    }
  }

  // ============ Zurücksetzen / Starten ============
  function reset() {
    bird = makeBird();
    pipes = [];
    particles = [];
    score = 0;
    frameCount = 0;
    shake = 0;
    groundX = 0;
    initPipes();
    initBg();
    hudScore.textContent = '0';
    hud.classList.remove('has-new-best');
  }

  function startGame() {
    state = STATE.PLAYING;
    startScreen.classList.add('hidden');
    startScreen.classList.remove('visible');
    hud.classList.add('visible');
    bird.vy = -7.2;
    sfx.flap();
  }

  function gameOver() {
    state = STATE.DYING;
    sfx.hit();
    setTimeout(sfx.die, 120);
    shake = 18;
    spawnParticles(bird.x, bird.y, '#ffd93b', 24, 5);
    spawnParticles(bird.x, bird.y, '#ff6b6b', 16, 4);
    flashScreen();
    bird.alive = false;

    const isNewBest = score > best;
    if (isNewBest) {
      best = score;
      localStorage.setItem('sunsetFlierBest', String(best));
    }

    setTimeout(() => {
      state = STATE.OVER;
      endScore.textContent = score;
      endBest.textContent = best;
      const medal = getMedal(score);
      if (medal) {
        medalEl.style.display = 'block';
        medalEl.textContent = medal.symbol;
        medalEl.style.color = medal.color;
      } else {
        medalEl.style.display = 'none';
      }
      endScreen.classList.remove('hidden');
      endScreen.classList.add('visible');
      if (isNewBest) {
        setTimeout(()=>hud.classList.add('has-new-best'), 0);
      }
    }, 700);
  }

  function flashScreen() {
    flashEl.style.transition = 'none';
    flashEl.style.opacity = '0.9';
    requestAnimationFrame(() => {
      flashEl.style.transition = 'opacity 0.4s ease-out';
      flashEl.style.opacity = '0';
    });
  }

  function getMedal(s) {
    if (s >= 30) return { symbol: '★', color: '#ffd93b' };
    if (s >= 20) return { symbol: '✦', color: '#c0c0c0' };
    if (s >= 10) return { symbol: '✧', color: '#cd7f32' };
    if (s >= 5)  return { symbol: '•', color: '#ff9ff3' };
    return null;
  }

  function restart() {
    reset();
    state = STATE.READY;
    endScreen.classList.add('hidden');
    endScreen.classList.remove('visible');
    startScreen.classList.remove('hidden');
    startScreen.classList.add('visible');
    hud.classList.remove('visible');
    hud.classList.remove('has-new-best');
  }

  // ============ Eingabe ============
  function flap() {
    if (state === STATE.READY) { startGame(); return; }
    if (state === STATE.PLAYING) {
      bird.vy = -7.2;
      bird.wingPhase = 0;
      sfx.flap();
      spawnParticles(bird.x - 8, bird.y + 6, '#fff8d6', 4, 1.5);
    }
    if (state === STATE.OVER) {
      // Tippen irgendwo auf dem Endbildschirm startet ebenfalls neu
      // aber wir lassen hauptsächlich den Button das übernehmen
    }
  }

  function handleInput(e) {
    if (e.target.closest('button')) return;
    e.preventDefault();
    flap();
  }

  frame.addEventListener('mousedown', handleInput);
  frame.addEventListener('touchstart', handleInput, { passive: false });
  window.addEventListener('keydown', (e) => {
    if (e.code === 'Space' || e.code === 'ArrowUp' || e.code === 'Enter') {
      e.preventDefault();
      if (state === STATE.OVER) restart();
      else flap();
    }
  });
  restartBtn.addEventListener('click', (e) => { e.stopPropagation(); restart(); });

  // ============ Aktualisierung ============
  const GRAVITY = 0.42;
  const MAX_FALL = 9;

  function update() {
    frameCount++;
    if (state === STATE.READY) {
      // ruhiges Schaukeln
      bird.y = H * 0.5 + Math.sin(frameCount * 0.08) * 14;
      bird.wingPhase += 0.2;
      bird.rotation = Math.sin(frameCount * 0.08) * 0.05;
    }

    if (state === STATE.PLAYING) {
      bird.vy += GRAVITY;
      if (bird.vy > MAX_FALL) bird.vy = MAX_FALL;
      bird.y += bird.vy;
      bird.wingPhase += 0.4;
      bird.rotation = Math.max(-0.4, Math.min(1.2, bird.vy * 0.08));

      // Rohre
      for (const p of pipes) {
        p.x -= PIPE_SPEED;
        if (!p.scored && p.x + PIPE_W < bird.x) {
          p.scored = true;
          score++;
          hudScore.textContent = score;
          sfx.score();
          spawnParticles(bird.x, bird.y, '#ffd93b', 8, 2);
        }
      }
      // Rohre außerhalb des Bildschirms entfernen, neue hinzufügen
      while (pipes.length > 0 && pipes[0].x + PIPE_W < -10) pipes.shift();
      const last = pipes[pipes.length - 1];
      if (last && last.x < W - PIPE_SPACING) {
        pipes.push(makePipe(last.x + PIPE_SPACING));
      }

      // Kollision
      // Boden & Decke
      const groundY = H - 80;
      if (bird.y + bird.radius > groundY || bird.y - bird.radius < 0) {
        bird.y = Math.max(bird.radius + 1, Math.min(groundY - bird.radius, bird.y));
        gameOver();
        return;
      }
      // Rohre
      for (const p of pipes) {
        if (p.x > bird.x + bird.radius || p.x + PIPE_W < bird.x - bird.radius) continue;
        const top = p.gapY;
        const bottom = p.gapY + PIPE_GAP;
        if (bird.y - bird.radius < top || bird.y + bird.radius > bottom) {
          // Kollision
          gameOver();
          return;
        }
      }
    }

    if (state === STATE.DYING) {
      bird.vy += GRAVITY * 1.2;
      bird.y += bird.vy;
      bird.rotation += 0.06;
      const groundY = H - 80;
      if (bird.y + bird.radius > groundY) {
        bird.y = groundY - bird.radius;
        bird.vy = 0;
      }
    }

    // Partikel
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      p.x += p.vx;
      p.y += p.vy;
      p.vy += 0.15;
      p.life -= 0.02;
      if (p.life <= 0) particles.splice(i, 1);
    }

    // Abklingen des Zitterns
    if (shake > 0) shake = Math.max(0, shake - 1.2);

    // Hintergrundanimationen
    for (const c of clouds) {
      c.x -= c.speed;
      if (c.x < -100) c.x = W + 100;
    }
    for (const s of stars) {
      s.tw += 0.05;
    }

    groundX = (groundX - PIPE_SPEED) % 120;
  }

  // ============ Darstellung ============
  function render() {
    ctx.save();
    // Zittern
    if (shake > 0) {
      const sx = (Math.random() - 0.5) * shake;
      const sy = (Math.random() - 0.5) * shake;
      ctx.translate(sx, sy);
    }

    // Himmelsverlauf
    const sky = ctx.createLinearGradient(0, 0, 0, H);
    sky.addColorStop(0, '#1a1746');
    sky.addColorStop(0.3, '#4a3a8e');
    sky.addColorStop(0.55, '#ff6b6b');
    sky.addColorStop(0.8, '#feca50');
    sky.addColorStop(1, '#ffd93b');
    ctx.fillStyle = sky;
    ctx.fillRect(-20, -20, W + 40, H + 40);

    // Sterne
    for (const s of stars) {
      const a = 0.4 + Math.sin(s.tw) * 0.4;
      ctx.fillStyle = `rgba(255,255,255,${a})`;
      ctx.beginPath();
      ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
      ctx.fill();
    }

    // Sonne
    const sunX = W * 0.75;
    const sunY = H * 0.65;
    const sunGlow = ctx.createRadialGradient(sunX, sunY, 0, sunX, sunY, 180);
    sunGlow.addColorStop(0, 'rgba(255,240,160,0.7)');
    sunGlow.addColorStop(0.4, 'rgba(255,200,120,0.3)');
    sunGlow.addColorStop(1, 'rgba(255,200,120,0)');
    ctx.fillStyle = sunGlow;
    ctx.fillRect(sunX - 200, sunY - 200, 400, 400);

    ctx.fillStyle = '#fff6d0';
    ctx.beginPath();
    ctx.arc(sunX, sunY, 42, 0, Math.PI * 2);
    ctx.fill();

    // Wolken
    for (const c of clouds) {
      drawCloud(c.x, c.y, c.scale);
    }

    // Bergsilhouette
    ctx.fillStyle = 'rgba(40, 30, 80, 0.7)';
    ctx.beginPath();
    ctx.moveTo(0, H * 0.72);
    const peaks = [
      [W * 0.15, H * 0.58],
      [W * 0.25, H * 0.68],
      [W * 0.4, H * 0.52],
      [W * 0.55, H * 0.66],
      [W * 0.7, H * 0.6],
      [W * 0.85, H * 0.68],
      [W, H * 0.62]
    ];
    for (const [px, py] of peaks) ctx.lineTo(px, py);
    ctx.lineTo(W, H);
    ctx.lineTo(0, H);
    ctx.closePath();
    ctx.fill();

    // Rohre
    for (const p of pipes) {
      drawPipe(p);
    }

    // Ground
    drawGround();

    // Bird
    drawBird();

    // Partikel
    for (const part of particles) {
      ctx.fillStyle = part.color;
      ctx.globalAlpha = part.life;
      ctx.beginPath();
      ctx.arc(part.x, part.y, part.size, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.globalAlpha = 1;

    ctx.restore();
  }

  function drawCloud(x, y, scale) {
    ctx.fillStyle = 'rgba(255, 230, 200, 0.85)';
    const s = scale;
    ctx.beginPath();
    ctx.arc(x, y, 18 * s, 0, Math.PI * 2);
    ctx.arc(x + 18 * s, y + 4 * s, 22 * s, 0, Math.PI * 2);
    ctx.arc(x + 38 * s, y, 16 * s, 0, Math.PI * 2);
    ctx.arc(x + 18 * s, y - 6 * s, 14 * s, 0, Math.PI * 2);
    ctx.fill();
  }

  function drawPipe(p) {
    const x = p.x;
    const topY = p.gapY;
    const bottomY = p.gapY + PIPE_GAP;
    const groundY = H - 80;

    // Verlauf
    const grad = ctx.createLinearGradient(x, 0, x + PIPE_W, 0);
    grad.addColorStop(0, '#2d5a1a');
    grad.addColorStop(0.15, '#4a902b');
    grad.addColorStop(0.5, '#7bc94a');
    grad.addColorStop(0.85, '#4a902b');
    grad.addColorStop(1, '#2d5a1a');

    // Oberes Rohr
    ctx.fillStyle = grad;
    ctx.fillRect(x, 0, PIPE_W, topY - 18);
    // Obere Kappe
    ctx.fillRect(x - 4, topY - 18, PIPE_W + 8, 18);
    // Hervorhebung der oberen Kappe
    ctx.fillStyle = 'rgba(255,255,255,0.15)';
    ctx.fillRect(x - 4, topY - 18, 14, 18);

    // Unteres Rohr
    ctx.fillStyle = grad;
    ctx.fillRect(x, bottomY + 18, PIPE_W, groundY - (bottomY + 18));
    // Untere Kappe
    ctx.fillRect(x - 4, bottomY, PIPE_W + 8, 18);
    ctx.fillStyle = 'rgba(255,255,255,0.15)';
    ctx.fillRect(x - 4, bottomY, 14, 18);

    // Kontur
    ctx.strokeStyle = '#1c3a0d';
    ctx.lineWidth = 2;
    ctx.strokeRect(x, 0, PIPE_W, topY - 18);
    ctx.strokeRect(x - 4, topY - 18, PIPE_W + 8, 18);
    ctx.strokeRect(x, bottomY + 18, PIPE_W, groundY - (bottomY + 18));
    ctx.strokeRect(x - 4, bottomY, PIPE_W + 8, 18);
  }

  function drawGround() {
    const groundY = H - 80;
    // Sand-/Abenddämmerungsboden
    const g = ctx.createLinearGradient(0, groundY, 0, H);
    g.addColorStop(0, '#8b5a2b');
    g.addColorStop(0.4, '#5e3a1c');
    g.addColorStop(1, '#2d1a0c');
    ctx.fillStyle = g;
    ctx.fillRect(0, groundY, W, 80);

    // Oberflächenlinie
    ctx.strokeStyle = '#1c3a0d';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(0, groundY);
    ctx.lineTo(W, groundY);
    ctx.stroke();

    // Musterstreifen (beweglich)
    ctx.fillStyle = 'rgba(0,0,0,0.15)';
    for (let i = -2; i < W / 40 + 2; i++) {
      const stripeX = (i * 80 + groundX + 120) % (W + 80) - 40;
      ctx.beginPath();
      ctx.moveTo(stripeX, groundY);
      ctx.lineTo(stripeX + 30, groundY + 80);
      ctx.lineTo(stripeX + 50, groundY + 80);
      ctx.lineTo(stripeX + 20, groundY);
      ctx.closePath();
      ctx.fill();
    }

    // Kieselsteine oben
    ctx.fillStyle = '#3d2412';
    for (let i = 0; i < 12; i++) {
      const px = (i * 60 + groundX + 60) % (W + 40);
      ctx.beginPath();
      ctx.arc(px, groundY + 4, 3, 0, Math.PI * 2);
      ctx.fill();
    }
  }

  function drawBird() {
    const b = bird;
    ctx.save();
    ctx.translate(b.x, b.y);
    ctx.rotate(b.rotation);

    // Schatten
    ctx.fillStyle = 'rgba(0,0,0,0.2)';
    ctx.beginPath();
    ctx.ellipse(2, 2, b.radius, b.radius * 0.85, 0, 0, Math.PI * 2);
    ctx.fill();

    // Körper
    const bodyGrad = ctx.createRadialGradient(-4, -4, 2, 0, 0, b.radius + 4);
    bodyGrad.addColorStop(0, '#ff9ff3');
    bodyGrad.addColorStop(0.5, '#ff6b6b');
    bodyGrad.addColorStop(1, '#c0334a');
    ctx.fillStyle = bodyGrad;
    ctx.beginPath();
    ctx.ellipse(0, 0, b.radius, b.radius * 0.92, 0, 0, Math.PI * 2);
    ctx.fill();

    // Flügel (schlagend)
    const wingY = Math.sin(b.wingPhase) * 8;
    ctx.fillStyle = '#ffd93b';
    ctx.beginPath();
    ctx.ellipse(-4, 4 + wingY, 9, 5, -0.4 + wingY * 0.05, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = '#b87c0a';
    ctx.lineWidth = 1.5;
    ctx.stroke();

    // Auge
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(8, -4, 5, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#1a1746';
    ctx.beginPath();
    ctx.arc(9.5, -4, 2.5, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(10, -5, 1, 0, Math.PI * 2);
    ctx.fill();

    // Schnabel
    ctx.fillStyle = '#ffaa33';
    ctx.beginPath();
    ctx.moveTo(13, -1);
    ctx.lineTo(20, 1);
    ctx.lineTo(13, 3);
    ctx.closePath();
    ctx.fill();
    ctx.strokeStyle = '#b87c0a';
    ctx.lineWidth = 1;
    ctx.stroke();

    // Schwanz
    ctx.fillStyle = '#ffd93b';
    ctx.beginPath();
    ctx.moveTo(-12, -2);
    ctx.lineTo(-18, -5);
    ctx.lineTo(-16, 0);
    ctx.lineTo(-18, 5);
    ctx.lineTo(-12, 3);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();

    ctx.restore();
  }

  // ============ Schleife ============
  function loop() {
    update();
    render();
    requestAnimationFrame(loop);
  }

  reset();
  loop();
})();
</script>
</body>
</html>
```

{% endcode %}

</details>

<details>

<summary>Gesamte Konversation</summary>

{% code overflow="wrap" expandable="true" %}

````
[Denken beginnen]
Der Nutzer möchte ein kurzes Flappy-Bird-Spiel. Ich werde eine vollständige, eigenständige HTML-Datei erstellen mit:

1. Ein klassisches Spiel im Flappy-Bird-Stil
2. Schöne Grafiken und Animationen
3. Punktestand-Erfassung
4. Game-Over-Bildschirm
5. Neustart-Funktion
6. Gute Physik (Schwerkraft, Sprungmechanik)
7. Sich bewegende und zufällig platzierte Rohre
8. Soundeffekte (mit der Web Audio API für Töne)
9. Ein schöner visueller Stil – ich vermeide den klassischen gelben Vogel und verwende ein interessanteres Farbschema

Ich werde es mit Folgendem gestalten:
- Ein schöner Himmelshintergrund mit Farbverlauf
- Animierter Vogel mit schlagenden Flügeln
- Rohre mit einem schönen grünen/metallischen Look
- Punktanzeige
- Startbildschirm, Spielzustand, Game-Over-Zustand
- Bestpunktestand-Speicherung mit localStorage
- Partikeleffekte beim Punkten
- Flüssige Animationen

Ich werde eine Sonnenuntergangs-/Dämmerungs-Farbpalette verwenden, um es unverwechselbar zu machen – warme Orange- und Lilatöne, wobei der Vogel eine warme Korallen-/Rotfarbe hat.

Ich werde das sorgfältig programmieren:

Hauptfunktionen:
- Rendering auf Canvas-Basis
- Game-Loop mit requestAnimationFrame
- Vogelphysik: Schwerkraft, Sprungimpuls
- Rohr-Erzeugung: zufällige Lücken, Bewegung nach links
- Kollisionserkennung
- Punktzuwachs beim Passieren von Rohren
- Partikeleffekte beim Punktgewinn
- Todesanimation
- localStorage für den Bestpunktestand
- Soundeffekte über die Web Audio API
- Mobil-/Touch-Unterstützung
- Sauberes, ausgefeiltes UI-Overlay

Ich werde den Code schreiben.
[Denken beendet]

Ich werde ein ausgefeiltes Flappy-Bird-Spiel mit einer unverwechselbaren Dämmerungs-/Sonnenuntergangs-Ästhetik, flüssiger Physik, Partikeleffekten und Sound erstellen.

```html
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Sonnenuntergangs-Flieger</title>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Fred:wght@400;700&display=swap" rel="stylesheet">
<style>
  :root {
    --sunset-1: #ff6b6b;
    --sunset-2: #feca50;
    --sunset-3: #ff9ff3;
    --dusk: #36306b;
    --night: #1a1746;
    --accent: #ffd93b;
    --coral: #ff6b6b;
    --pipe: #4a902b;
    --pipe-dark: #2d5a1a;
  }

  * { margin: 0; padding: 0; box-sizing: border-box; }

  html, body {
    height: 100%;
    width: 100%;
    overflow: hidden;
    background: var(--night);
    font-family: 'Fred', sans-serif;
    -webkit-user-select: none;
    user-select: none;
    touch-action: manipulation;
  }

  #game-wrap {
    position: relative;
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background: linear-gradient(180deg, #1a1746 0%, #36306b 40%, #ff6b6b 70%, #feca50 100%);
  }

  #game-frame {
    position: relative;
    width: min(100vw, 480px);
    height: min(100vh, 720px);
    max-height: 100vh;
    box-shadow: 0 30px 80px rgba(0,0,0,0.6), inset 0 0 0 1px rgba(255,255,255,0.05);
    overflow: hidden;
    background: linear-gradient(180deg, #4a3a8e 0%, #ff6b6b 60%, #feca50 100%);
  }

  canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
  }

  .overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    pointer-events: none;
    z-index: 10;
    transition: opacity 0.3s ease;
  }

  .overlay.hidden {
    opacity: 0;
    pointer-events: none;
  }

  .overlay.visible {
    opacity: 1;
    pointer-events: auto;
  }

  .panel {
    background: rgba(26, 23, 70, 0.85);
    border: 3px solid var(--accent);
    border-radius: 16px;
    padding: 28px 36px;
    text-align: center;
    color: #fff;
    box-shadow: 0 12px 0 rgba(0,0,0,0.3), 0 0 40px rgba(255, 217, 59, 0.4);
    backdrop-filter: blur(4px);
    transform: translateY(0);
    animation: bob 3s ease-in-out infinite;
  }

  @keyframes bob {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-6px); }
  }

  .title {
    font-family: 'Press Start 2P', monospace;
    font-size: 26px;
    color: var(--accent);
    text-shadow: 3px 3px 0 #b87c0a, 6px 6px 0 rgba(0,0,0,0.3);
    letter-spacing: 1px;
    margin-bottom: 6px;
    line-height: 1.3;
  }

  .subtitle {
    font-size: 14px;
    color: #ffe8a8;
    margin-bottom: 20px;
    font-weight: 700;
  }

  .tap-icon {
    font-size: 42px;
    margin: 8px 0;
    animation: tap 1.2s ease-in-out infinite;
  }

  @keyframes tap {
    0%, 50% { transform: translateY(0) scale(1); }
    20% { transform: translateY(-8px) scale(1.1); }
  }

  .instructions {
    font-size: 13px;
    color: #fff;
    opacity: 0.85;
    margin-top: 10px;
    font-weight: 400;
  }

  .score-row {
    display: flex;
    gap: 24px;
    justify-content: center;
    margin: 12px 0 20px;
  }

  .score-box {
    background: rgba(0,0,0,0.4);
    border: 2px solid var(--accent);
    border-radius: 10px;
    padding: 10px 18px;
    min-width: 80px;
  }

  .score-box .label {
    font-family: 'Press Start 2p', monospace;
    font-size: 9px;
    color: var(--accent);
    margin-bottom: 4px;
    letter-spacing: 1px;
  }

  .score-box .value {
    font-family: 'Press Start 2p', monospace;
    font-size: 18px;
    color: #fff;
  }

  .score-box.best .value { color: var(--coral); }

  .btn {
    font-family: 'Press Start 2p', monospace;
    font-size: 12px;
    color: var(--night);
    background: var(--accent);
    border: none;
    padding: 12px 22px;
    border-radius: 8px;
    cursor: pointer;
    letter-spacing: 1px;
    box-shadow: 0 6px 0 #b87c0a, 0 8px 12px rgba(0,0,0,0.3);
    transition: transform 0.1s, box-shadow 0.1s;
    pointer-events: auto;
  }

  .btn:hover { transform: translateY(2px); box-shadow: 0 4px 0 #b87c0a, 0 6px 10px rgba(0,0,0,0.3); }
  .btn:active { transform: translateY(6px); box-shadow: 0 0 0 #b87c0a, 0 2px 6px rgba(0,0,0,0.3); }

  #hud {
    position: absolute;
    top: 24px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 5;
    font-family: 'Press Start 2P', monospace;
    font-size: 36px;
    color: #fff;
    text-shadow: 3px 3px 0 #b87c0a, 5px 5px 0 rgba(0,0,0,0.5);
    pointer-events: none;
    transition: opacity 0.3s;
    opacity: 0;
  }

  #hud.visible { opacity: 1; }

  #hud .new-best {
    font-size: 11px;
    color: var(--coral);
    text-shadow: 2px 2px 0 #000;
    margin-top: 8px;
    opacity: 0;
    transition: opacity 0.3s;
  }

  #hud.has-new-best .new-best { opacity: 1; animation: pulse 0.6s ease infinite alternate; }

  @keyframes pulse {
    from { transform: translateX(-50%) scale(1); }
    to { transform: translateX(-50%) scale(1.15); }
  }

  .medal {
    font-family: 'Press Start 2P', monospace;
    font-size: 48px;
    margin: 10px 0;
    text-shadow: 3px 3px 0 #000;
  }

  .flash {
    position: absolute;
    inset: 0;
    background: #fff;
    opacity: 0;
    pointer-events: none;
    z-index: 8;
  }

  .footer {
    position: absolute;
    bottom: 12px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 11px;
    color: rgba(255,255,255,0.7);
    font-weight: 400;
    text-align: center;
    pointer-events: none;
    z-index: 9;
  }
</style>
</head>
<body>
<div id="game-wrap">
  <div id="game-frame">
    <canvas id="canvas"></canvas>
    <div id="hud" class="hud">
      <div id="hud-score">0</div>
      <div class="new-best">NEUER BESTWERT!</div>
    </div>
    <div id="flash" class="flash"></div>

    <!-- Startbildschirm -->
    <div id="start-screen" class="overlay visible">
      <div class="panel">
        <div class="title">SONNENUNTERGANG<br>FLIEGER</div>
        <div class="subtitle">— Dämmerungsflug —</div>
        <div class="tap-icon">✊</div>
        <div class="instructions">TIPPEN / LEERTASTE / KLICKEN zum Flattern</div>
      </div>
    </div>

    <!-- Bildschirm nach Spielende -->
    <div id="end-screen" class="overlay hidden">
      <div class="panel">
        <div class="title" style="font-size:22px;color:var(--coral);">SPIEL VORBEI</div>
        <div id="medal" class="medal" style="display:none;">★</div>
        <div class="score-row">
          <div class="score-box">
            <div class="label">PUNKTE</div>
            <div class="value" id="end-score">0</div>
          </div>
          <div class="score-box best">
            <div class="label">BESTWERT</div>
            <div class="value" id="end-best">0</div>
          </div>
        </div>
        <button class="btn" id="restart-btn">NOCHMAL</button>
      </div>
    </div>

    <div class="footer">Ton an • Tippen zum Fliegen</div>
  </div>
</div>

<script>
(() => {
  // ============ Einrichtung ============
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  const frame = document.getElementById('game-frame');
  const hud = document.getElementById('hud');
  const hudScore = document.getElementById('hud-score');
  const startScreen = document.getElementById('start-screen');
  const endScreen = document.getElementById('end-screen');
  const endScore = document.getElementById('end-score');
  const endBest = document.getElementById('end-best');
  const medalEl = document.getElementById('medal');
  const flashEl = document.getElementById('flash');
  const restartBtn = document.getElementById('restart-btn');

  let W = 480, H = 720;
  const dpr = window.devicePixelRatio || 1;

  function resize() {
    const rect = frame.getBoundingClientRect();
    W = rect.width;
    H = rect.height;
    canvas.width = W * dpr;
    canvas.height = H * dpr;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }
  window.addEventListener('resize', resize);
  resize();

  // ============ Audio (Web Audio API) ============
  let audioCtx = null;
  function getAudio() {
    if (!audioCtx) {
      try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); }
      catch(e) { audioCtx = null; }
    }
    if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
    return audioCtx;
  }

  function beep(freq, dur=0.1, type='square', vol=0.1, slideTo=null) {
    const ac = getAudio();
    if (!ac) return;
    const osc = ac.createOscillator();
    const gain = ac.createGain();
    osc.type = type;
    osc.frequency.value = freq;
    if (slideTo) osc.frequency.linearRampToValueAtTime(slideTo, ac.currentTime + dur);
    gain.gain.value = vol;
    gain.gain.exponentialRampToValueAtTime(0.001, ac.currentTime + dur);
    osc.connect(gain);
    gain.connect(ac.destination);
    osc.start();
    osc.stop(ac.currentTime + dur);
  }

  const sfx = {
    flap: () => beep(420, 0.12, 'square', 0.08, 720),
    score: () => { beep(880, 0.08, 'triangle', 0.12); setTimeout(()=>beep(1320, 0.1, 'triangle', 0.1), 60); },
    hit: () => { beep(180, 0.25, 'sawtooth', 0.15, 60); },
    die: () => { beep(120, 0.5, 'sawtooth', 0.15, 40); }
  };

  // ============ Spielstatus ============
  const STATE = { READY: 0, PLAYING: 1, DYING: 2, OVER: 3 };
  let state = STATE.READY;

  let bird, pipes, particles, score, best, frameCount, shake, groundX, clouds, stars;

  best = parseInt(localStorage.getItem('sunsetFlierBest') || '0', 10) || 0;
  endBest.textContent = best;

  // ============ Vogel ============
  function makeBird() {
    return {
      x: W * 0.32,
      y: H * 0.5,
      vy: 0,
      radius: 14,
      wingPhase: 0,
      rotation: 0,
      alive: true
    };
  }

  // ============ Rohre ============
  const PIPE_W = 68;
  const PIPE_GAP = 180;
  const PIPE_SPEED = 2.6;
  const PIPE_SPACING = 220;

  function makePipe(x) {
    const minY = 90;
    const maxY = H - 90 - PIPE_GAP;
    const gapY = minY + Math.random() * (maxY - minY);
    return { x, gapY, scored: false, passed: false };
  }

  function initPipes() {
    pipes = [];
    let x = W + 80;
    for (let i = 0; i < 4; i++) {
      pipes.push(makePipe(x));
      x += PIPE_SPACING;
    }
  }

  // ============ Partikel ============
  function spawnParticles(x, y, color, count=10, vel=3) {
    for (let i = 0; i < count; i++) {
      const angle = Math.random() * Math.PI * 2;
      const speed = (Math.random() * 0.6 + 0.4) * vel;
      particles.push({
        x, y,
        vx: Math.cos(angle) * speed,
        vy: Math.sin(angle) * speed - 1,
        life: 1,
        size: Math.random() * 4 + 2,
        color
      });
    }
  }

  // ============ Hintergrund ============
  function initBg() {
    clouds = [];
    for (let i = 0; i < 4; i++) {
      clouds.push({
        x: Math.random() * W,
        y: 80 + Math.random() * 180,
        scale: 0.6 + Math.random() * 0.8,
        speed: 0.2 + Math.random() * 0.3
      });
    }
    stars = [];
    for (let i = 0; i < 30; i++) {
      stars.push({
        x: Math.random() * W,
        y: Math.random() * H * 0.4,
        r: Math.random() * 1.5 + 0.3,
        tw: Math.random() * Math.PI * 2
      });
    }
  }

  // ============ Zurücksetzen / Starten ============
  function reset() {
    bird = makeBird();
    pipes = [];
    particles = [];
    score = 0;
    frameCount = 0;
    shake = 0;
    groundX = 0;
    initPipes();
    initBg();
    hudScore.textContent = '0';
    hud.classList.remove('has-new-best');
  }

  function startGame() {
    state = STATE.PLAYING;
    startScreen.classList.add('hidden');
    startScreen.classList.remove('visible');
    hud.classList.add('visible');
    bird.vy = -7.2;
    sfx.flap();
  }

  function gameOver() {
    state = STATE.DYING;
    sfx.hit();
    setTimeout(sfx.die, 120);
    shake = 18;
    spawnParticles(bird.x, bird.y, '#ffd93b', 24, 5);
    spawnParticles(bird.x, bird.y, '#ff6b6b', 16, 4);
    flashScreen();
    bird.alive = false;

    const isNewBest = score > best;
    if (isNewBest) {
      best = score;
      localStorage.setItem('sunsetFlierBest', String(best));
    }

    setTimeout(() => {
      state = STATE.OVER;
      endScore.textContent = score;
      endBest.textContent = best;
      const medal = getMedal(score);
      if (medal) {
        medalEl.style.display = 'block';
        medalEl.textContent = medal.symbol;
        medalEl.style.color = medal.color;
      } else {
        medalEl.style.display = 'none';
      }
      endScreen.classList.remove('hidden');
      endScreen.classList.add('visible');
      if (isNewBest) {
        setTimeout(()=>hud.classList.add('has-new-best'), 0);
      }
    }, 700);
  }

  function flashScreen() {
    flashEl.style.transition = 'none';
    flashEl.style.opacity = '0.9';
    requestAnimationFrame(() => {
      flashEl.style.transition = 'opacity 0.4s ease-out';
      flashEl.style.opacity = '0';
    });
  }

  function getMedal(s) {
    if (s >= 30) return { symbol: '★', color: '#ffd93b' };
    if (s >= 20) return { symbol: '✦', color: '#c0c0c0' };
    if (s >= 10) return { symbol: '✧', color: '#cd7f32' };
    if (s >= 5)  return { symbol: '•', color: '#ff9ff3' };
    return null;
  }

  function restart() {
    reset();
    state = STATE.READY;
    endScreen.classList.add('hidden');
    endScreen.classList.remove('visible');
    startScreen.classList.remove('hidden');
    startScreen.classList.add('visible');
    hud.classList.remove('visible');
    hud.classList.remove('has-new-best');
  }

  // ============ Eingabe ============
  function flap() {
    if (state === STATE.READY) { startGame(); return; }
    if (state === STATE.PLAYING) {
      bird.vy = -7.2;
      bird.wingPhase = 0;
      sfx.flap();
      spawnParticles(bird.x - 8, bird.y + 6, '#fff8d6', 4, 1.5);
    }
    if (state === STATE.OVER) {
      // Tippen irgendwo auf dem Endbildschirm startet ebenfalls neu
      // aber wir lassen hauptsächlich den Button das übernehmen
    }
  }

  function handleInput(e) {
    if (e.target.closest('button')) return;
    e.preventDefault();
    flap();
  }

  frame.addEventListener('mousedown', handleInput);
  frame.addEventListener('touchstart', handleInput, { passive: false });
  window.addEventListener('keydown', (e) => {
    if (e.code === 'Space' || e.code === 'ArrowUp' || e.code === 'Enter') {
      e.preventDefault();
      if (state === STATE.OVER) restart();
      else flap();
    }
  });
  restartBtn.addEventListener('click', (e) => { e.stopPropagation(); restart(); });

  // ============ Aktualisierung ============
  const GRAVITY = 0.42;
  const MAX_FALL = 9;

  function update() {
    frameCount++;
    if (state === STATE.READY) {
      // ruhiges Schaukeln
      bird.y = H * 0.5 + Math.sin(frameCount * 0.08) * 14;
      bird.wingPhase += 0.2;
      bird.rotation = Math.sin(frameCount * 0.08) * 0.05;
    }

    if (state === STATE.PLAYING) {
      bird.vy += GRAVITY;
      if (bird.vy > MAX_FALL) bird.vy = MAX_FALL;
      bird.y += bird.vy;
      bird.wingPhase += 0.4;
      bird.rotation = Math.max(-0.4, Math.min(1.2, bird.vy * 0.08));

      // Rohre
      for (const p of pipes) {
        p.x -= PIPE_SPEED;
        if (!p.scored && p.x + PIPE_W < bird.x) {
          p.scored = true;
          score++;
          hudScore.textContent = score;
          sfx.score();
          spawnParticles(bird.x, bird.y, '#ffd93b', 8, 2);
        }
      }
      // Rohre außerhalb des Bildschirms entfernen, neue hinzufügen
      while (pipes.length > 0 && pipes[0].x + PIPE_W < -10) pipes.shift();
      const last = pipes[pipes.length - 1];
      if (last && last.x < W - PIPE_SPACING) {
        pipes.push(makePipe(last.x + PIPE_SPACING));
      }

      // Kollision
      // Boden & Decke
      const groundY = H - 80;
      if (bird.y + bird.radius > groundY || bird.y - bird.radius < 0) {
        bird.y = Math.max(bird.radius + 1, Math.min(groundY - bird.radius, bird.y));
        gameOver();
        return;
      }
      // Rohre
      for (const p of pipes) {
        if (p.x > bird.x + bird.radius || p.x + PIPE_W < bird.x - bird.radius) continue;
        const top = p.gapY;
        const bottom = p.gapY + PIPE_GAP;
        if (bird.y - bird.radius < top || bird.y + bird.radius > bottom) {
          // Kollision
          gameOver();
          return;
        }
      }
    }

    if (state === STATE.DYING) {
      bird.vy += GRAVITY * 1.2;
      bird.y += bird.vy;
      bird.rotation += 0.06;
      const groundY = H - 80;
      if (bird.y + bird.radius > groundY) {
        bird.y = groundY - bird.radius;
        bird.vy = 0;
      }
    }

    // Partikel
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      p.x += p.vx;
      p.y += p.vy;
      p.vy += 0.15;
      p.life -= 0.02;
      if (p.life <= 0) particles.splice(i, 1);
    }

    // Abklingen des Zitterns
    if (shake > 0) shake = Math.max(0, shake - 1.2);

    // Hintergrundanimationen
    for (const c of clouds) {
      c.x -= c.speed;
      if (c.x < -100) c.x = W + 100;
    }
    for (const s of stars) {
      s.tw += 0.05;
    }

    groundX = (groundX - PIPE_SPEED) % 120;
  }

  // ============ Darstellung ============
  function render() {
    ctx.save();
    // Zittern
    if (shake > 0) {
      const sx = (Math.random() - 0.5) * shake;
      const sy = (Math.random() - 0.5) * shake;
      ctx.translate(sx, sy);
    }

    // Himmelsverlauf
    const sky = ctx.createLinearGradient(0, 0, 0, H);
    sky.addColorStop(0, '#1a1746');
    sky.addColorStop(0.3, '#4a3a8e');
    sky.addColorStop(0.55, '#ff6b6b');
    sky.addColorStop(0.8, '#feca50');
    sky.addColorStop(1, '#ffd93b');
    ctx.fillStyle = sky;
    ctx.fillRect(-20, -20, W + 40, H + 40);

    // Sterne
    for (const s of stars) {
      const a = 0.4 + Math.sin(s.tw) * 0.4;
      ctx.fillStyle = `rgba(255,255,255,${a})`;
      ctx.beginPath();
      ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
      ctx.fill();
    }

    // Sonne
    const sunX = W * 0.75;
    const sunY = H * 0.65;
    const sunGlow = ctx.createRadialGradient(sunX, sunY, 0, sunX, sunY, 180);
    sunGlow.addColorStop(0, 'rgba(255,240,160,0.7)');
    sunGlow.addColorStop(0.4, 'rgba(255,200,120,0.3)');
    sunGlow.addColorStop(1, 'rgba(255,200,120,0)');
    ctx.fillStyle = sunGlow;
    ctx.fillRect(sunX - 200, sunY - 200, 400, 400);

    ctx.fillStyle = '#fff6d0';
    ctx.beginPath();
    ctx.arc(sunX, sunY, 42, 0, Math.PI * 2);
    ctx.fill();

    // Wolken
    for (const c of clouds) {
      drawCloud(c.x, c.y, c.scale);
    }

    // Bergsilhouette
    ctx.fillStyle = 'rgba(40, 30, 80, 0.7)';
    ctx.beginPath();
    ctx.moveTo(0, H * 0.72);
    const peaks = [
      [W * 0.15, H * 0.58],
      [W * 0.25, H * 0.68],
      [W * 0.4, H * 0.52],
      [W * 0.55, H * 0.66],
      [W * 0.7, H * 0.6],
      [W * 0.85, H * 0.68],
      [W, H * 0.62]
    ];
    for (const [px, py] of peaks) ctx.lineTo(px, py);
    ctx.lineTo(W, H);
    ctx.lineTo(0, H);
    ctx.closePath();
    ctx.fill();

    // Rohre
    for (const p of pipes) {
      drawPipe(p);
    }

    // Ground
    drawGround();

    // Bird
    drawBird();

    // Partikel
    for (const part of particles) {
      ctx.fillStyle = part.color;
      ctx.globalAlpha = part.life;
      ctx.beginPath();
      ctx.arc(part.x, part.y, part.size, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.globalAlpha = 1;

    ctx.restore();
  }

  function drawCloud(x, y, scale) {
    ctx.fillStyle = 'rgba(255, 230, 200, 0.85)';
    const s = scale;
    ctx.beginPath();
    ctx.arc(x, y, 18 * s, 0, Math.PI * 2);
    ctx.arc(x + 18 * s, y + 4 * s, 22 * s, 0, Math.PI * 2);
    ctx.arc(x + 38 * s, y, 16 * s, 0, Math.PI * 2);
    ctx.arc(x + 18 * s, y - 6 * s, 14 * s, 0, Math.PI * 2);
    ctx.fill();
  }

  function drawPipe(p) {
    const x = p.x;
    const topY = p.gapY;
    const bottomY = p.gapY + PIPE_GAP;
    const groundY = H - 80;

    // Verlauf
    const grad = ctx.createLinearGradient(x, 0, x + PIPE_W, 0);
    grad.addColorStop(0, '#2d5a1a');
    grad.addColorStop(0.15, '#4a902b');
    grad.addColorStop(0.5, '#7bc94a');
    grad.addColorStop(0.85, '#4a902b');
    grad.addColorStop(1, '#2d5a1a');

    // Oberes Rohr
    ctx.fillStyle = grad;
    ctx.fillRect(x, 0, PIPE_W, topY - 18);
    // Obere Kappe
    ctx.fillRect(x - 4, topY - 18, PIPE_W + 8, 18);
    // Hervorhebung der oberen Kappe
    ctx.fillStyle = 'rgba(255,255,255,0.15)';
    ctx.fillRect(x - 4, topY - 18, 14, 18);

    // Unteres Rohr
    ctx.fillStyle = grad;
    ctx.fillRect(x, bottomY + 18, PIPE_W, groundY - (bottomY + 18));
    // Untere Kappe
    ctx.fillRect(x - 4, bottomY, PIPE_W + 8, 18);
    ctx.fillStyle = 'rgba(255,255,255,0.15)';
    ctx.fillRect(x - 4, bottomY, 14, 18);

    // Kontur
    ctx.strokeStyle = '#1c3a0d';
    ctx.lineWidth = 2;
    ctx.strokeRect(x, 0, PIPE_W, topY - 18);
    ctx.strokeRect(x - 4, topY - 18, PIPE_W + 8, 18);
    ctx.strokeRect(x, bottomY + 18, PIPE_W, groundY - (bottomY + 18));
    ctx.strokeRect(x - 4, bottomY, PIPE_W + 8, 18);
  }

  function drawGround() {
    const groundY = H - 80;
    // Sand-/Abenddämmerungsboden
    const g = ctx.createLinearGradient(0, groundY, 0, H);
    g.addColorStop(0, '#8b5a2b');
    g.addColorStop(0.4, '#5e3a1c');
    g.addColorStop(1, '#2d1a0c');
    ctx.fillStyle = g;
    ctx.fillRect(0, groundY, W, 80);

    // Oberflächenlinie
    ctx.strokeStyle = '#1c3a0d';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(0, groundY);
    ctx.lineTo(W, groundY);
    ctx.stroke();

    // Musterstreifen (beweglich)
    ctx.fillStyle = 'rgba(0,0,0,0.15)';
    for (let i = -2; i < W / 40 + 2; i++) {
      const stripeX = (i * 80 + groundX + 120) % (W + 80) - 40;
      ctx.beginPath();
      ctx.moveTo(stripeX, groundY);
      ctx.lineTo(stripeX + 30, groundY + 80);
      ctx.lineTo(stripeX + 50, groundY + 80);
      ctx.lineTo(stripeX + 20, groundY);
      ctx.closePath();
      ctx.fill();
    }

    // Kieselsteine oben
    ctx.fillStyle = '#3d2412';
    for (let i = 0; i < 12; i++) {
      const px = (i * 60 + groundX + 60) % (W + 40);
      ctx.beginPath();
      ctx.arc(px, groundY + 4, 3, 0, Math.PI * 2);
      ctx.fill();
    }
  }

  function drawBird() {
    const b = bird;
    ctx.save();
    ctx.translate(b.x, b.y);
    ctx.rotate(b.rotation);

    // Schatten
    ctx.fillStyle = 'rgba(0,0,0,0.2)';
    ctx.beginPath();
    ctx.ellipse(2, 2, b.radius, b.radius * 0.85, 0, 0, Math.PI * 2);
    ctx.fill();

    // Körper
    const bodyGrad = ctx.createRadialGradient(-4, -4, 2, 0, 0, b.radius + 4);
    bodyGrad.addColorStop(0, '#ff9ff3');
    bodyGrad.addColorStop(0.5, '#ff6b6b');
    bodyGrad.addColorStop(1, '#c0334a');
    ctx.fillStyle = bodyGrad;
    ctx.beginPath();
    ctx.ellipse(0, 0, b.radius, b.radius * 0.92, 0, 0, Math.PI * 2);
    ctx.fill();

    // Flügel (schlagend)
    const wingY = Math.sin(b.wingPhase) * 8;
    ctx.fillStyle = '#ffd93b';
    ctx.beginPath();
    ctx.ellipse(-4, 4 + wingY, 9, 5, -0.4 + wingY * 0.05, 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = '#b87c0a';
    ctx.lineWidth = 1.5;
    ctx.stroke();

    // Auge
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(8, -4, 5, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#1a1746';
    ctx.beginPath();
    ctx.arc(9.5, -4, 2.5, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(10, -5, 1, 0, Math.PI * 2);
    ctx.fill();

    // Schnabel
    ctx.fillStyle = '#ffaa33';
    ctx.beginPath();
    ctx.moveTo(13, -1);
    ctx.lineTo(20, 1);
    ctx.lineTo(13, 3);
    ctx.closePath();
    ctx.fill();
    ctx.strokeStyle = '#b87c0a';
    ctx.lineWidth = 1;
    ctx.stroke();

    // Schwanz
    ctx.fillStyle = '#ffd93b';
    ctx.beginPath();
    ctx.moveTo(-12, -2);
    ctx.lineTo(-18, -5);
    ctx.lineTo(-16, 0);
    ctx.lineTo(-18, 5);
    ctx.lineTo(-12, 3);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();

    ctx.restore();
  }

  // ============ Schleife ============
  function loop() {
    update();
    render();
    requestAnimationFrame(loop);
  }

  reset();
  loop();
})();
</script>
</body>
</html>
```

Hier ist, was ich gebaut habe:

**Thema**: "Sunset Flier" — statt des klassischen grün/türkisen Flappy-Bird-Looks habe ich eine Dämmerungs-/Sonnenuntergangspalette gewählt (tiefes Violett → Koralle → Gold) mit silhouettierten Bergen, einer leuchtenden Sonne, dahintreibenden Wolken und funkelnden Sternen im oberen Himmel.

**Vogel**: Ein korallenrosa Vogel mit gelbem schlagendem Flügel, animiertem Schnabel, Schwanz und sich mitbewegendem Auge. Die Flügelschläge werden physisch animiert, wenn der Vogel aufsteigt/fällt.

**Mechanik**:
- Klassische Schwerkraft + Flap-Impuls-Physik, fein abgestimmt, damit sie sich reaktionsschnell anfühlt
- Rohre mit zufälliger Lückenplatzierung, sanftem Scrollen und einer Darstellung von Kappen mit Hervorhebung für einen klobigen 2D-Look
- Punktzuwachs, wenn der Vogel an einem Rohr vorbeifliegt, mit Partikel-Explosion und Klingel-SFX
- Kollision mit Rohren, Decke und Boden löst ein Game Over mit Bildschirmblitz, Bildschirmzittern und Todespartikeln aus

**Politur**:
- Press Start 2P-Pixel-Schrift-HUD mit Punktestand und "NEUER BESTWERT!"-Puls bei neuen Rekorden
- Game-Over-Panel mit Medaillensystem (★ / ✦ / ✧ / •) basierend auf Punkteschwellen
- Bestpunktestand per `localStorage` gespeichert
- Animierter Startbildschirm mit schwebendem Panel
- Prozedurale Soundeffekte über die Web Audio API (Flap, Punkt, Treffer, Tod) — keine externen Assets
- Bewegte Bodenstreifen/Kieselsteine, um das Geschwindigkeitsgefühl zu verstärken
- Responsiv: skaliert auf den Viewport, unterstützt Maus, Touch und Tastatur (Leertaste/↑/Enter)
- Neustart-Button + Tippen überall zum Neustart

Einfach die Datei im Browser öffnen — tippen/klicken/Leertaste drücken, um zu fliegen.
````

{% endcode %}

</details>

Und das Spiel hat Sound und funktioniert wunderbar! Zur Erinnerung: Das war eine 1-Bit-Quantisierung und sie hat gut funktioniert!

<figure><img src="/files/b94a29cea0745996500d31a0c31f900e21541960" alt="" width="375"><figcaption></figcaption></figure>
{% endstep %}
{% endstepper %}

### :triangular\_ruler:Langer Kontext mittels KV-Cache-Quantisierung

Um langen Kontext in llama.cpp zu nutzen, müssen wir KV-Cache-Quantisierung einsetzen, um den Speicherverbrauch zu reduzieren. Kürzlich hat llama.cpp der KV-Cache-Quantisierung Tricks für höhere Genauigkeit hinzugefügt - [siehe](https://github.com/ggml-org/llama.cpp/pull/21038) und andere PRs!

Derzeit werden diese KV-Cache-Datentypen unterstützt:

{% code overflow="wrap" expandable="true" %}

```
f32, f16, bf16, q8_0, q4_0, q4_1, iq4_nl, q5_0, q5_1
```

{% endcode %}

Standardmäßig `f16` wird verwendet. Wenn Sie `q4_0` das etwa 4,5 Bit pro Gewicht entspricht, können Sie auf etwa 16 / 4,5 = **3,5-mal längere Kontextlängen**! Wenn Ihr Modell also früher 10K unterstützte, könnten 35K in Reichweite liegen! `q4_1` ist wahrscheinlich besser, da Sie auch einen Shifting-Parameter erhalten, und es sind 5 Bit pro Gewicht – also 3,2-mal längere Kontexte.

Verwenden Sie es wie unten:

{% code overflow="wrap" expandable="true" %}

```bash
./llama.cpp/llama-cli \
    --model unsloth/GLM-5.2-GGUF/UD-IQ2_M/GLM-5.2-UD-IQ2_M-00001-of-00006.gguf \
    --temp 1.0 \
    --top-p 0.95 \
    --min-p 0.01 \\
    --cache-type-k q4_1 \\
    --cache-type-v q4_1
```

{% endcode %}

## 📊 Benchmarks

Weiter unten können Sie die GLM-5.2-Benchmarks in Tabellenform ansehen:

<div><figure><img src="/files/d36314cd3c3ee4a27c581d8311f2b7ed99024848" alt=""><figcaption></figcaption></figure> <figure><img src="/files/a3863bfa3eaaf2544155f887c971195c6c2eb616" alt=""><figcaption></figcaption></figure></div>

| Benchmark                                      | GLM-5.2 | Claude Opus 4.8 | GPT-5.5 | Gemini 3.1 Pro | GLM-5.1 | Qwen3.7-Max | MiniMax M3 | DeepSeek-V4-Pro |
| ---------------------------------------------- | :-----: | :-------------: | :-----: | :------------: | :-----: | :---------: | :--------: | :-------------: |
| **Schlussfolgern**                             |         |                 |         |                |         |             |            |                 |
| HLE                                            |   40.5  |      49.8\*     |  41.4\* |       45       |    31   |     41.4    |     37     |       37.7      |
| HLE (mit Tools)                                |   54.7  |      57.9\*     |  52.2\* |     51.4\*     |   52.3  |     53.5    |      -     |       48.2      |
| CritPt                                         |   20.9  |       20.9      |   27.1  |      17.7      |   4.6   |     13.4    |     3.7    |       12.9      |
| AIME 2026                                      |   99.2  |       95.7      |   98.3  |      98.2      |   95.3  |      97     |      -     |       94.6      |
| HMMT Nov. 2025                                 |   94.4  |       96.5      |   96.5  |      94.8      |    94   |      95     |    84.4    |       94.4      |
| HMMT Feb. 2026                                 |   92.5  |       96.7      |   96.7  |      87.3      |   82.6  |     97.1    |    84.4    |       95.2      |
| IMOAnswerBench                                 |   91.0  |       83.5      |    -    |       81       |   83.8  |      90     |      -     |       89.8      |
| GPQA-Diamond                                   |   91.2  |       93.6      |   93.6  |      94.3      |   86.2  |      90     |     93     |       90.1      |
| **Programmierung**                             |         |                 |         |                |         |             |            |                 |
| SWE-bench Pro                                  |   62.1  |       69.2      |   58.6  |      54.2      |   58.4  |     60.6    |     59     |       55.4      |
| NL2Repo                                        |   48.9  |       69.7      |   50.7  |      33.4      |   42.7  |     47.2    |    42.1    |       35.5      |
| DeepSWE                                        |   46.2  |        58       |    70   |       10       |    18   |      18     |     20     |        8        |
| ProgramBench                                   |   63.7  |       71.9      |   70.8  |      39.5      |   50.9  |      -      |      -     |       47.8      |
| Terminal Bench 2.1 (Terminus-2)                |   81.0  |        85       |    84   |       74       |   63.5  |      75     |     65     |        64       |
| Terminal Bench 2.1 (bestes gemeldetes Harness) |   82.7  |       78.9      |   83.4  |      70.7      |    69   |      -      |      -     |        -        |
| FrontierSWE (Dominanz)                         |   74.4  |       75.1      |   72.6  |      39.6      |   30.5  |      -      |      -     |       29.0      |
| PostTrainBench                                 |   34.3  |       37.2      |   28.4  |      21.6      |   20.1  |      -      |      -     |        -        |
| SWE-Marathon                                   |   13.0  |       26.0      |   12.0  |       4.0      |   1.0   |      -      |      -     |        -        |
| **Agentenbasiert**                             |         |                 |         |                |         |             |            |                 |
| MCP-Atlas (öffentlicher Satz)                  |   76.8  |       77.8      |   75.3  |      69.2      |   71.8  |     76.4    |    74.2    |       73.6      |
| Tool-Decathlon                                 |   48.2  |       59.9      |   55.6  |      48.8      |   40.7  |      -      |      -     |       52.8      |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://unsloth.ai/docs/de/modelle/glm-5.2.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
