App: Webtechnologien HTML, CSS und Java für Einsteiger visualisieren

✅ Freie Nutzung von Text & Abbildungen & Video mit einem Unidigital-Zugang

Über das Vibe Coding lassen sich sinnvolle Anwendungen entwickeln, um zum Beispiel die Unterschiede der Frontend-Webtechnologien HTML, CSS und JavaScript spielerisch zu erklären. Nicht selten bewirkt eine gute Visualisierung mehr als Textabsätze und eine einfache 2D-Grafik. Im Beispiel wurde die Canvas-Funktion in ChatGPT verwendet.

Prompt mit ChatGPT:

Entwickle eine interaktive Lern-App, die Anfängern verständlich zeigt, aus welchen Bestandteilen eine App besteht – insbesondere im Zusammenspiel von HTML, CSS und JavaScript.

Im Zentrum der App befindet sich eine visuelle Szene. Um diese Szene herum sind mehrere Felder angeordnet. Jedes Feld enthält Beispielcode aus HTML, CSS oder JavaScript. Die Felder sollen farblich unterschiedlich gestaltet sein und zusätzlich anzeigen, welchen Anteil dieser Code an der gesamten App hat.

Wenn man mit der Maus über ein Code-Feld fährt, soll in der zentralen Szene direkt sichtbar werden, welcher Teil der Szene durch diesen Code gesteuert oder beeinflusst wird. So soll deutlich werden, welcher Code für welche Visualisierung, welches Verhalten oder welche Gestaltung zuständig ist.

Die Nutzer sollen zwischen verschiedenen Szenen wechseln oder Szenen überspringen können. Jede neue Szene soll anhand eines anderen Beispiels verdeutlichen, wie HTML, CSS und JavaScript zusammenwirken.

Ziel der App ist es, Einsteigern klarzumachen, dass eine App aus verschiedenen Code-Bestandteilen besteht und dass jeder dieser Bestandteile eine bestimmte Aufgabe übernimmt: HTML für die Struktur, CSS für das Design und JavaScript für Interaktionen und Verhalten.

Nachdem ich den Ausgangs­prompt für die App mit ChatGPT formuliert, in einen neuen Chat eingefügt und mit Enter bestätigt habe, startet der Entwicklungsprozess. Dies dauert nur wenige Minuten. Anschließend wird die Anwendung direkt in den Chat integriert. Oben rechts über das Icon mit den zwei Pfeilen öffnet sich der Vollbildmodus.  

Nach einem Klick oben links kann ich nun den gesamten Code der App einsehen, der von ChatGPT generiert wurde. Wichtig ist hierbei, solchen „Vibe Code“ nicht direkt in einen HTML-Block auf der eigenen Webseite einzufügen. Dies kann funktionieren oder der Code enthält fehlerhafte Elemente, die sich womöglich erst später negativ auf den Betrieb der eigenen Webseite auswirken. Vorsicht!

Gesamter Code der App - Code NICHT auf der eigenen Webseite ausführen!

<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Code-Bausteine einer App verstehen</title>
  <style>
    :root {
      --html: #ff7a45;
      --css: #3b82f6;
      --js: #facc15;
      --bg: #0f172a;
      --panel: rgba(15, 23, 42, 0.82);
      --text: #e5e7eb;
      --muted: #94a3b8;
      --border: rgba(255,255,255,0.15);
    }

    * { box-sizing: border-box; }

    body {
      margin: 0;
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      background:
        radial-gradient(circle at top left, rgba(59, 130, 246, 0.25), transparent 34%),
        radial-gradient(circle at bottom right, rgba(250, 204, 21, 0.16), transparent 30%),
        var(--bg);
      color: var(--text);
      min-height: 100vh;
      overflow-x: hidden;
    }

    header {
      text-align: center;
      padding: 28px 20px 12px;
    }

    h1 {
      margin: 0 0 10px;
      font-size: clamp(1.8rem, 4vw, 3.2rem);
      letter-spacing: -0.04em;
    }

    header p {
      margin: 0 auto;
      max-width: 860px;
      color: var(--muted);
      line-height: 1.55;
      font-size: 1rem;
    }

    .controls {
      margin-top: 18px;
      display: flex;
      justify-content: center;
      align-items: center;
      gap: 12px;
      flex-wrap: wrap;
    }

    button {
      border: 1px solid var(--border);
      background: rgba(255,255,255,0.09);
      color: var(--text);
      padding: 10px 16px;
      border-radius: 999px;
      font-weight: 700;
      cursor: pointer;
      transition: transform 0.2s ease, background 0.2s ease;
    }

    button:hover {
      transform: translateY(-2px);
      background: rgba(255,255,255,0.16);
    }

    #sceneLabel {
      color: var(--muted);
      font-weight: 700;
    }

    .app-layout {
      max-width: 1280px;
      margin: 20px auto 36px;
      padding: 18px;
      display: grid;
      grid-template-columns: minmax(260px, 1fr) minmax(360px, 620px) minmax(260px, 1fr);
      gap: 18px;
      align-items: center;
    }

    .code-column {
      display: flex;
      flex-direction: column;
      gap: 16px;
    }

    .code-card {
      position: relative;
      background: var(--panel);
      border: 1px solid var(--border);
      border-left: 8px solid var(--card-color);
      border-radius: 18px;
      padding: 16px;
      min-height: 175px;
      box-shadow: 0 24px 60px rgba(0,0,0,0.25);
      cursor: pointer;
      transition: transform 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease;
      overflow: hidden;
    }

    .code-card::before {
      content: "";
      position: absolute;
      inset: 0;
      background: linear-gradient(135deg, color-mix(in srgb, var(--card-color) 20%, transparent), transparent 45%);
      opacity: 0.65;
      pointer-events: none;
    }

    .code-card:hover,
    .code-card.active {
      transform: translateY(-5px) scale(1.015);
      border-color: color-mix(in srgb, var(--card-color) 70%, white);
      box-shadow: 0 28px 70px color-mix(in srgb, var(--card-color) 25%, rgba(0,0,0,0.5));
    }

    .code-card > * { position: relative; z-index: 1; }

    .tag {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      font-size: 0.8rem;
      font-weight: 900;
      letter-spacing: 0.08em;
      text-transform: uppercase;
      color: white;
      background: color-mix(in srgb, var(--card-color) 70%, black);
      padding: 6px 10px;
      border-radius: 999px;
      margin-bottom: 12px;
    }

    .share {
      float: right;
      color: var(--card-color);
      font-weight: 900;
      font-size: 0.92rem;
    }

    .code-card h2 {
      font-size: 1.02rem;
      margin: 0 0 10px;
    }

    pre {
      margin: 0;
      white-space: pre-wrap;
      word-break: break-word;
      color: #dbeafe;
      font-size: 0.84rem;
      line-height: 1.45;
    }

    .scene-shell {
      border: 1px solid var(--border);
      border-radius: 28px;
      background: rgba(255,255,255,0.08);
      box-shadow: 0 30px 80px rgba(0,0,0,0.35);
      padding: 18px;
      position: relative;
    }

    canvas {
      display: block;
      width: 100%;
      aspect-ratio: 1 / 1;
      border-radius: 22px;
      background: #111827;
      border: 1px solid rgba(255,255,255,0.12);
    }

    .info {
      margin-top: 14px;
      background: rgba(0,0,0,0.24);
      border: 1px solid var(--border);
      border-radius: 18px;
      padding: 14px;
      min-height: 102px;
    }

    .info strong { color: white; }
    .info p {
      margin: 6px 0 0;
      color: var(--muted);
      line-height: 1.5;
    }

    .legend {
      display: flex;
      justify-content: center;
      gap: 10px;
      flex-wrap: wrap;
      margin-top: 14px;
    }

    .legend span {
      display: inline-flex;
      align-items: center;
      gap: 7px;
      color: var(--muted);
      font-size: 0.9rem;
    }

    .dot {
      width: 11px;
      height: 11px;
      border-radius: 50%;
      background: var(--dot);
    }

    footer {
      color: var(--muted);
      text-align: center;
      padding: 0 20px 28px;
      font-size: 0.92rem;
    }

    @media (max-width: 1040px) {
      .app-layout { grid-template-columns: 1fr; }
      .code-column {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
      }
      .scene-shell { order: -1; }
    }
  </style>
</head>
<body>
  <header>
    <h1>Wie entsteht eine App?</h1>
    <p>
      HTML baut die Struktur, CSS malt und gestaltet sie, JavaScript bringt Bewegung und Verhalten hinein.
      Fahre mit der Maus über ein Code-Feld, um den Unterschied direkt in der Canvas-Szene zu sehen.
    </p>
    <div class="controls">
      <button id="prevBtn">← Vorherige Szene</button>
      <span id="sceneLabel">Szene 1 / 3</span>
      <button id="nextBtn">Szene überspringen →</button>
    </div>
  </header>

  <main class="app-layout">
    <section class="code-column" id="leftColumn"></section>

    <section class="scene-shell">
      <canvas id="sceneCanvas" width="900" height="900"></canvas>

      <div class="info" id="infoBox">
        <strong>Bewege die Maus über ein Code-Feld.</strong>
        <p>HTML zeigt jetzt stärker die Grundstruktur. CSS wird als Pinsel dargestellt, der Farben und Gestaltung sichtbar aufträgt.</p>
      </div>

      <div class="legend">
        <span><i class="dot" style="--dot: var(--html)"></i>HTML = Struktur / Bauteile</span>
        <span><i class="dot" style="--dot: var(--css)"></i>CSS = Farben / Gestaltung</span>
        <span><i class="dot" style="--dot: var(--js)"></i>JavaScript = Verhalten / Dynamik</span>
      </div>
    </section>

    <section class="code-column" id="rightColumn"></section>
  </main>

  <footer>
    Lernidee: Anfänger sollen klar erkennen, dass HTML die Bausteine liefert, CSS sie sichtbar gestaltet und JavaScript Verhalten ergänzt.
  </footer>

  <script>
    const canvas = document.getElementById("sceneCanvas");
    const ctx = canvas.getContext("2d");
    const leftColumn = document.getElementById("leftColumn");
    const rightColumn = document.getElementById("rightColumn");
    const infoBox = document.getElementById("infoBox");
    const sceneLabel = document.getElementById("sceneLabel");

    const colors = {
      html: "#ff7a45",
      css: "#3b82f6",
      js: "#facc15"
    };

    const neutral = {
      line: "#cbd5e1",
      fill: "#0f172a",
      panel: "#1e293b",
      soft: "#94a3b8"
    };

    const scenes = [
      {
        title: "Login-App",
        description: "Ein Login-Bereich: HTML baut die Eingaben, CSS malt Karte und Button, JavaScript reagiert auf Klicks.",
        cards: [
          {
            id: "structure",
            lang: "HTML",
            color: colors.html,
            share: "35 %",
            title: "Struktur der Oberfläche",
            code: `<section class="login">
  <h2>Willkommen</h2>
  <input placeholder="E-Mail">
  <input placeholder="Passwort">
  <button>Einloggen</button>
</section>`,
            info: "HTML ist das Gerüst. Es bestimmt, dass es eine Karte, eine Überschrift, zwei Eingabefelder und einen Button gibt.",
            side: "left"
          },
          {
            id: "style",
            lang: "CSS",
            color: colors.css,
            share: "40 %",
            title: "Gestaltung und Layout",
            code: `.login {
  background: white;
  border-radius: 24px;
  box-shadow: 0 20px 60px #0004;
}
button {
  background: linear-gradient(...);
}`,
            info: "CSS ist hier bewusst wie ein Pinsel dargestellt: Es färbt die vorher neutrale Struktur ein und gibt ihr Stil, Tiefe und Wirkung.",
            side: "left"
          },
          {
            id: "behavior",
            lang: "JavaScript",
            color: colors.js,
            share: "25 %",
            title: "Interaktion beim Klick",
            code: `button.addEventListener("click", () => {
  checkInputs();
  showSuccess();
});`,
            info: "JavaScript reagiert auf Benutzeraktionen, prüft Eingaben und blendet Zustände oder Meldungen ein.",
            side: "right"
          }
        ]
      },
      {
        title: "Wetter-App",
        description: "Eine Wetterkarte: HTML liefert Bereiche, CSS macht daraus Himmel und Karte, JavaScript aktualisiert Daten.",
        cards: [
          {
            id: "structure",
            lang: "HTML",
            color: colors.html,
            share: "30 %",
            title: "Datenbereiche",
            code: `<article class="weather">
  <h2>Berlin</h2>
  <div class="temp">24°C</div>
  <div class="icon">☀️</div>
</article>`,
            info: "HTML legt fest, dass es Textbereiche für Ort, Temperatur und Symbol gibt.",
            side: "left"
          },
          {
            id: "style",
            lang: "CSS",
            color: colors.css,
            share: "45 %",
            title: "Visuelle Atmosphäre",
            code: `.weather {
  background: skyblue;
  color: white;
}
.icon {
  filter: drop-shadow(...);
}`,
            info: "Der CSS-Pinsel malt Himmel, Farbverlauf, Kartenoptik und Kontraste auf die strukturierte Fläche.",
            side: "right"
          },
          {
            id: "behavior",
            lang: "JavaScript",
            color: colors.js,
            share: "25 %",
            title: "Aktuelle Werte",
            code: `async function loadWeather() {
  const data = await fetchWeather();
  updateTemperature(data.temp);
}`,
            info: "JavaScript lädt Daten und ersetzt Inhalte dynamisch, ohne die ganze Seite neu aufzubauen.",
            side: "right"
          }
        ]
      },
      {
        title: "Mini-Spiel",
        description: "Ein kleines Spiel: HTML stellt Bühne und Anzeige bereit, CSS gestaltet die Welt, JavaScript bewegt alles.",
        cards: [
          {
            id: "structure",
            lang: "HTML",
            color: colors.html,
            share: "20 %",
            title: "Spielfläche",
            code: `<canvas id="game"></canvas>
<div class="score">Punkte: 0</div>`,
            info: "HTML schafft die Grundfläche des Spiels, die Punkteanzeige und die Platzhalter für Spielobjekte wie Sterne.",
            side: "left"
          },
          {
            id: "style",
            lang: "CSS",
            color: colors.css,
            share: "30 %",
            title: "Spiel-Look",
            code: `canvas {
  border-radius: 20px;
  background: radial-gradient(...);
}
.score {
  font-weight: bold;
}`,
            info: "CSS gibt der Bühne und den Sternen ihr Aussehen. Die Sterne sind hier gestaltet, aber noch nicht die eigentliche Logik.",
            side: "left"
          },
          {
            id: "behavior",
            lang: "JavaScript",
            color: colors.js,
            share: "50 %",
            title: "Bewegung und Regeln",
            code: `function update() {
  player.x += speed;
  detectCollision();
  drawGame();
}`,
            info: "JavaScript bewegt die Spielfigur, prüft die Berührung mit Sternen, sammelt sie ein und verändert den Punktestand.",
            side: "right"
          }
        ]
      }
    ];

    let sceneIndex = 0;
    let activeId = null;
    let frame = 0;

    function makeCard(card) {
      const el = document.createElement("article");
      el.className = "code-card";
      el.style.setProperty("--card-color", card.color);
      el.dataset.id = card.id;

      el.innerHTML = `
        <span class="tag">${card.lang}</span>
        <span class="share">${card.share}</span>
        <h2>${card.title}</h2>
        <pre><code></code></pre>
      `;
      el.querySelector("code").textContent = card.code;

      el.addEventListener("mouseenter", () => setActive(card.id));
      el.addEventListener("focus", () => setActive(card.id));
      el.addEventListener("mouseleave", () => setActive(null));
      el.tabIndex = 0;
      return el;
    }

    function renderCards() {
      leftColumn.innerHTML = "";
      rightColumn.innerHTML = "";

      scenes[sceneIndex].cards.forEach(card => {
        const el = makeCard(card);
        if (card.side === "left") leftColumn.appendChild(el);
        else rightColumn.appendChild(el);
      });

      sceneLabel.textContent = `Szene ${sceneIndex + 1} / ${scenes.length}: ${scenes[sceneIndex].title}`;
      setActive(null);
    }

    function setActive(id) {
      activeId = id;

      document.querySelectorAll(".code-card").forEach(card => {
        card.classList.toggle("active", card.dataset.id === id);
      });

      const activeCard = scenes[sceneIndex].cards.find(card => card.id === id);

      if (!activeCard) {
        infoBox.innerHTML = `
          <strong>${scenes[sceneIndex].title}</strong>
          <p>${scenes[sceneIndex].description} HTML erscheint als neutrale Struktur, CSS malt diese Struktur per Pinsel aus, JavaScript zeigt Verhalten und Bewegung.</p>
        `;
        return;
      }

      infoBox.innerHTML = `
        <strong style="color:${activeCard.color}">${activeCard.lang}: ${activeCard.title}</strong>
        <p>${activeCard.info}</p>
      `;
    }

    function roundedRect(x, y, w, h, r) {
      ctx.beginPath();
      ctx.moveTo(x + r, y);
      ctx.lineTo(x + w - r, y);
      ctx.quadraticCurveTo(x + w, y, x + w, y + r);
      ctx.lineTo(x + w, y + h - r);
      ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
      ctx.lineTo(x + r, y + h);
      ctx.quadraticCurveTo(x, y + h, x, y + h - r);
      ctx.lineTo(x, y + r);
      ctx.quadraticCurveTo(x, y, x + r, y);
      ctx.closePath();
    }

    function label(text, x, y, color) {
      ctx.save();
      ctx.font = "700 22px system-ui";
      ctx.fillStyle = color;
      ctx.textAlign = "center";
      ctx.fillText(text, x, y);
      ctx.restore();
    }

    function drawBase(title) {
      const w = canvas.width;
      const h = canvas.height;
      ctx.clearRect(0, 0, w, h);

      const gradient = ctx.createLinearGradient(0, 0, w, h);
      gradient.addColorStop(0, "#111827");
      gradient.addColorStop(1, "#020617");
      ctx.fillStyle = gradient;
      ctx.fillRect(0, 0, w, h);

      ctx.save();
      ctx.globalAlpha = 0.18;
      for (let i = 0; i < 35; i++) {
        ctx.beginPath();
        ctx.arc((i * 83 + frame * 0.35) % w, (i * 47) % h, 2, 0, Math.PI * 2);
        ctx.fillStyle = "#ffffff";
        ctx.fill();
      }
      ctx.restore();

      ctx.font = "900 38px system-ui";
      ctx.textAlign = "center";
      ctx.fillStyle = "#ffffff";
      ctx.fillText(title, w / 2, 76);
    }

    function drawBrush(x, y, angle = -0.5) {
      ctx.save();
      ctx.translate(x, y);
      ctx.rotate(angle);

      ctx.fillStyle = "#8b5e3c";
      roundedRect(-10, -58, 18, 78, 8);
      ctx.fill();

      ctx.fillStyle = "#cbd5e1";
      roundedRect(-12, 8, 22, 18, 6);
      ctx.fill();

      ctx.beginPath();
      ctx.moveTo(-16, 24);
      ctx.lineTo(14, 24);
      ctx.lineTo(8, 52);
      ctx.lineTo(-10, 52);
      ctx.closePath();
      ctx.fillStyle = "#3b82f6";
      ctx.fill();

      ctx.restore();
    }

    function drawNormalSun(x, y, radius = 42, colored = true) {
      ctx.save();

      for (let i = 0; i < 12; i++) {
        const angle = (Math.PI * 2 / 12) * i;
        const x1 = x + Math.cos(angle) * (radius + 12);
        const y1 = y + Math.sin(angle) * (radius + 12);
        const x2 = x + Math.cos(angle) * (radius + 34);
        const y2 = y + Math.sin(angle) * (radius + 34);

        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.strokeStyle = colored ? "#fde047" : neutral.line;
        ctx.lineWidth = 6;
        ctx.lineCap = "round";
        ctx.stroke();
      }

      ctx.beginPath();
      ctx.arc(x, y, radius, 0, Math.PI * 2);
      ctx.fillStyle = colored ? "#facc15" : "#0b1220";
      ctx.fill();
      ctx.strokeStyle = colored ? "#fef08a" : neutral.line;
      ctx.lineWidth = 5;
      ctx.stroke();

      ctx.restore();
    }

    function cssRevealProgress() {
      return 0.18 + (Math.sin(frame / 18) * 0.5 + 0.5) * 0.72;
    }

    function applyCssPaint(drawColorVersion, brushYMin, brushYMax) {
      const progress = cssRevealProgress();
      const revealY = brushYMin + (brushYMax - brushYMin) * progress;

      ctx.save();
      ctx.beginPath();
      ctx.rect(0, 0, canvas.width, revealY);
      ctx.clip();
      drawColorVersion();
      ctx.restore();

      for (let i = 0; i < 4; i++) {
        ctx.save();
        ctx.globalAlpha = 0.08 - i * 0.015;
        ctx.fillStyle = colors.css;
        ctx.fillRect(120, revealY - 16 - i * 18, 660, 30);
        ctx.restore();
      }

      drawBrush(710, revealY - 14, -0.15);
    }

    function strokeNeutral(width = 2) {
      ctx.strokeStyle = neutral.line;
      ctx.lineWidth = width;
      ctx.stroke();
    }

    function drawLoginStructure() {
      ctx.save();
      roundedRect(210, 150, 480, 560, 38);
      ctx.fillStyle = activeId === "structure" ? "#182438" : neutral.panel;
      ctx.fill();
      strokeNeutral(activeId === "structure" ? 5 : 2);

      ctx.fillStyle = neutral.line;
      ctx.font = "900 42px system-ui";
      ctx.textAlign = "center";
      ctx.fillText("Willkommen", 450, 245);

      [315, 405].forEach((y, i) => {
        roundedRect(285, y, 330, 58, 16);
        ctx.fillStyle = "#0b1220";
        ctx.fill();
        strokeNeutral(activeId === "structure" ? 4 : 2);
        ctx.fillStyle = neutral.soft;
        ctx.font = "600 21px system-ui";
        ctx.fillText(i === 0 ? "E-Mail" : "Passwort", 450, y + 38);
      });

      roundedRect(315, 515, 270, 64, 18);
      ctx.fillStyle = "#0b1220";
      ctx.fill();
      strokeNeutral(activeId === "structure" ? 4 : 2);
      ctx.fillStyle = neutral.line;
      ctx.font = "900 24px system-ui";
      ctx.fillText("Einloggen", 450, 555);
      ctx.restore();
    }

    function drawLoginCss() {
      roundedRect(210, 150, 480, 560, 38);
      const cardGrad = ctx.createLinearGradient(210, 150, 690, 710);
      cardGrad.addColorStop(0, "#f8fafc");
      cardGrad.addColorStop(1, "#e2e8f0");
      ctx.fillStyle = cardGrad;
      ctx.shadowColor = "rgba(59,130,246,0.45)";
      ctx.shadowBlur = 35;
      ctx.fill();
      ctx.shadowBlur = 0;

      [315, 405].forEach((y) => {
        roundedRect(285, y, 330, 58, 16);
        ctx.fillStyle = "#e5e7eb";
        ctx.fill();
      });

      roundedRect(315, 515, 270, 64, 18);
      const btnGrad = ctx.createLinearGradient(315, 515, 585, 579);
      btnGrad.addColorStop(0, "#2563eb");
      btnGrad.addColorStop(1, "#7c3aed");
      ctx.fillStyle = btnGrad;
      ctx.fill();

      ctx.fillStyle = "#111827";
      ctx.font = "900 42px system-ui";
      ctx.textAlign = "center";
      ctx.fillText("Willkommen", 450, 245);

      ctx.fillStyle = "#64748b";
      ctx.font = "600 21px system-ui";
      ctx.fillText("E-Mail", 450, 353);
      ctx.fillText("Passwort", 450, 443);

      ctx.fillStyle = "white";
      ctx.font = "900 24px system-ui";
      ctx.fillText("Einloggen", 450, 555);
    }

    function drawLoginJs() {
      const pulse = Math.sin(frame / 12) * 8;
      roundedRect(330 - pulse / 2, 620 - pulse / 2, 240 + pulse, 48 + pulse, 16);
      ctx.fillStyle = activeId === "behavior" ? "#22c55e" : "#334155";
      ctx.fill();
      ctx.strokeStyle = colors.js;
      ctx.lineWidth = activeId === "behavior" ? 5 : 2;
      ctx.stroke();
      ctx.fillStyle = "#ffffff";
      ctx.font = "800 20px system-ui";
      ctx.textAlign = "center";
      ctx.fillText("✓ Klick löst Prüfung aus", 450, 652);
    }

    function drawWeatherStructure() {
      roundedRect(185, 155, 530, 555, 40);
      ctx.fillStyle = activeId === "structure" ? "#182438" : neutral.panel;
      ctx.fill();
      strokeNeutral(activeId === "structure" ? 5 : 2);

      ctx.fillStyle = neutral.line;
      ctx.textAlign = "center";
      ctx.font = "900 54px system-ui";
      ctx.fillText("Berlin", 450, 255);
      ctx.font = "900 92px system-ui";
      ctx.fillText("24°C", 450, 390);

      drawNormalSun(450, 510, 42, false);
    }

    function drawWeatherCss() {
      const sky = ctx.createLinearGradient(0, 160, 0, 690);
      sky.addColorStop(0, "#38bdf8");
      sky.addColorStop(1, "#2563eb");
      roundedRect(185, 155, 530, 555, 40);
      ctx.fillStyle = sky;
      ctx.shadowColor = "rgba(59,130,246,0.45)";
      ctx.shadowBlur = 35;
      ctx.fill();
      ctx.shadowBlur = 0;

      ctx.fillStyle = "#ffffff";
      ctx.textAlign = "center";
      ctx.font = "900 54px system-ui";
      ctx.fillText("Berlin", 450, 255);
      ctx.font = "900 92px system-ui";
      ctx.fillText("24°C", 450, 390);

      drawNormalSun(450, 510, 48, true);
    }

    function drawWeatherJs() {
      const offset = Math.sin(frame / 20) * 20;
      roundedRect(280, 605 + offset * 0.1, 340, 54, 18);
      ctx.fillStyle = "#0f172a";
      ctx.globalAlpha = activeId === "behavior" ? 0.95 : 0.65;
      ctx.fill();
      ctx.globalAlpha = 1;
      ctx.strokeStyle = colors.js;
      ctx.lineWidth = activeId === "behavior" ? 5 : 2;
      ctx.stroke();
      ctx.fillStyle = "#facc15";
      ctx.font = "800 22px system-ui";
      ctx.textAlign = "center";
      ctx.fillText("Aktualisiert durch Daten", 450, 640 + offset * 0.1);
    }

    function drawGameStructure() {
      roundedRect(155, 155, 590, 560, 30);
      ctx.fillStyle = activeId === "structure" ? "#182438" : neutral.panel;
      ctx.fill();
      strokeNeutral(activeId === "structure" ? 5 : 2);

      roundedRect(210, 200, 480, 405, 22);
      ctx.strokeStyle = neutral.line;
      ctx.lineWidth = activeId === "structure" ? 6 : 2;
      ctx.stroke();

      roundedRect(285, 635, 330, 54, 16);
      ctx.fillStyle = "#020617";
      ctx.fill();
      strokeNeutral(activeId === "structure" ? 4 : 2);
      ctx.fillStyle = neutral.line;
      ctx.font = "900 24px system-ui";
      ctx.textAlign = "center";
      ctx.fillText("Punkte: 120", 450, 670);
    }

    function drawGameCss() {
      roundedRect(155, 155, 590, 560, 30);
      const gameBg = ctx.createRadialGradient(450, 390, 80, 450, 390, 350);
      gameBg.addColorStop(0, "#1d4ed8");
      gameBg.addColorStop(1, "#172554");
      ctx.fillStyle = gameBg;
      ctx.fill();

      roundedRect(210, 200, 480, 405, 22);
      const field = ctx.createLinearGradient(210, 200, 690, 605);
      field.addColorStop(0, "#0ea5e9");
      field.addColorStop(1, "#0f172a");
      ctx.fillStyle = field;
      ctx.fill();

      ctx.font = "58px system-ui";
      ctx.textAlign = "center";
      ctx.fillText("⭐", 585, 300);
      ctx.fillText("⭐", 320, 535);

      roundedRect(285, 635, 330, 54, 16);
      ctx.fillStyle = "#0b1220";
      ctx.fill();
      ctx.fillStyle = "white";
      ctx.font = "900 24px system-ui";
      ctx.textAlign = "center";
      ctx.fillText("Punkte: 120", 450, 670);
    }

    function drawGameJs() {
      const t = frame / 55;
      const px = 450 + Math.cos(t) * 170;
      const py = 420 + Math.sin(t * 1.2) * 115;

      const targets = [
        { x: 585, y: 300 },
        { x: 320, y: 535 }
      ];

      targets.forEach(target => {
        const d = Math.hypot(px - target.x, py - target.y);
        const near = d < 72;

        ctx.beginPath();
        ctx.arc(target.x, target.y - 10, near ? 54 : 42, 0, Math.PI * 2);
        ctx.strokeStyle = near ? colors.js : "rgba(250,204,21,0.45)";
        ctx.lineWidth = near ? 6 : 2;
        ctx.setLineDash(near ? [] : [8, 8]);
        ctx.stroke();
        ctx.setLineDash([]);

        if (near) {
          ctx.fillStyle = "rgba(250,204,21,0.22)";
          ctx.fill();
          ctx.fillStyle = colors.js;
          ctx.font = "900 20px system-ui";
          ctx.textAlign = "center";
          ctx.fillText("+10", target.x, target.y - 72);
        }
      });

      ctx.beginPath();
      ctx.arc(px, py, 38, 0, Math.PI * 2);
      ctx.fillStyle = "#22c55e";
      ctx.fill();
      ctx.strokeStyle = colors.js;
      ctx.lineWidth = activeId === "behavior" ? 6 : 2;
      ctx.stroke();

      ctx.fillStyle = "#052e16";
      ctx.font = "900 28px system-ui";
      ctx.textAlign = "center";
      ctx.fillText("▶", px + 2, py + 10);

      ctx.beginPath();
      ctx.moveTo(450, 420);
      ctx.lineTo(px, py);
      ctx.strokeStyle = "rgba(250,204,21,0.55)";
      ctx.lineWidth = 3;
      ctx.setLineDash([10, 12]);
      ctx.stroke();
      ctx.setLineDash([]);
    }

    function drawLoginScene() {
      drawBase("Login-App");
      drawLoginStructure();

      if (activeId === "style") {
        applyCssPaint(drawLoginCss, 170, 710);
      } else if (!activeId) {
        drawLoginCss();
      }

      if (activeId === "behavior" || !activeId) {
        drawLoginJs();
      }

      if (activeId === "structure") label("HTML: Nur das Gerüst der Oberfläche", 450, 770, colors.html);
      if (activeId === "style") label("CSS: Der Pinsel färbt und gestaltet die Login-Struktur", 450, 770, colors.css);
      if (activeId === "behavior") label("JavaScript: Klicks lösen Verhalten aus", 450, 770, colors.js);
      if (!activeId) label("Komplettszene: Struktur + Gestaltung + Verhalten", 450, 770, "#e5e7eb");
    }

    function drawWeatherScene() {
      drawBase("Wetter-App");
      drawWeatherStructure();

      if (activeId === "style") {
        applyCssPaint(drawWeatherCss, 175, 700);
      } else if (!activeId) {
        drawWeatherCss();
      }

      if (activeId === "behavior" || !activeId) {
        drawWeatherJs();
      }

      if (activeId === "structure") label("HTML: Bereiche für Wetterdaten", 450, 770, colors.html);
      if (activeId === "style") label("CSS: Ein Pinsel malt Karte, Himmel und Farben", 450, 770, colors.css);
      if (activeId === "behavior") label("JavaScript: Daten werden aktualisiert", 450, 770, colors.js);
      if (!activeId) label("Komplettszene: Struktur + Gestaltung + Verhalten", 450, 770, "#e5e7eb");
    }

    function drawGameScene() {
      drawBase("Mini-Spiel");
      drawGameStructure();

      if (activeId === "style") {
        applyCssPaint(drawGameCss, 180, 715);
      } else if (!activeId) {
        drawGameCss();
      }

      if (activeId === "behavior" || !activeId) {
        drawGameJs();
      }

      if (activeId === "structure") label("HTML: Bühne und Punkteanzeige", 450, 770, colors.html);
      if (activeId === "style") label("CSS: Der Pinsel malt Bühne, Sterne und Punkteanzeige", 450, 770, colors.css);
      if (activeId === "behavior") label("JavaScript: Figur bewegt sich, sammelt Sterne und zählt Punkte", 450, 770, colors.js);
      if (!activeId) label("Komplettszene: Struktur + Gestaltung + Verhalten", 450, 770, "#e5e7eb");
    }

    function draw() {
      frame++;
      if (sceneIndex === 0) drawLoginScene();
      if (sceneIndex === 1) drawWeatherScene();
      if (sceneIndex === 2) drawGameScene();
      requestAnimationFrame(draw);
    }

    document.getElementById("nextBtn").addEventListener("click", () => {
      sceneIndex = (sceneIndex + 1) % scenes.length;
      renderCards();
    });

    document.getElementById("prevBtn").addEventListener("click", () => {
      sceneIndex = (sceneIndex - 1 + scenes.length) % scenes.length;
      renderCards();
    });

    renderCards();
    draw();
  </script>
</body>
</html>

In der App werden die drei Felder „HTML“, „CSS“ und „JavaScript“ angezeigt, in denen beispielhafter Code hinterlegt ist. Fahre ich mit der Maus über eines der Felder, werden die vom Code beeinflussten Bestandteile markiert. Die Auszeichnungssprache HTML bildet das Grundgerüst einer App oder Webseite, die Formatsprache CSS stellt unter anderem die Farbtöne dar und die Programmiersprache JavaScript ist für dynamische Elemente innerhalb der Anwendung zuständig. Genau diese Unterschiede sollen über die App möglichst verständlich visualisiert werden.

Es gibt insgesamt drei Szenarien, die sich über die Navigation auswählen lassen. Dazu gehören eine klassische Login-Maske, die Oberfläche einer Wetter-App und ein Spiel mit beweglichen Elementen. Über die Anwendung „Wie entsteht eine App?“ bekommt man nun ein besseres Gespür dafür, welche Aufgaben die einzelnen Sprachen übernehmen. Die App lässt sich als HTML-Datei herunterladen und in einem Browser öffnen.


Autor

matthias kindt

Matthias Kindt auf Linkedin