{"id":442,"date":"2026-05-16T21:55:26","date_gmt":"2026-05-16T19:55:26","guid":{"rendered":"https:\/\/robertoverkamp.com\/?page_id=442"},"modified":"2026-05-16T22:54:20","modified_gmt":"2026-05-16T20:54:20","slug":"landingpage-neu","status":"publish","type":"page","link":"https:\/\/robertoverkamp.com\/?page_id=442","title":{"rendered":"Landingpage &#8211; Neu"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"442\" class=\"elementor elementor-442\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8b1efdb e-flex e-con-boxed e-con e-parent\" data-id=\"8b1efdb\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7c75eca elementor-widget elementor-widget-html\" data-id=\"7c75eca\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!-- 1. p5.js Bibliothek laden -->\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/p5.js\/1.9.0\/p5.min.js\"><\/script>\n\n<!-- 2. Container f\u00fcr das interaktive Skript -->\n<div id=\"p5-canvas-container\" style=\"width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; overflow: hidden; z-index: 1;\"><\/div>\n\n<!-- 3. Das interaktive Skript -->\n<script>\nlet tiles = [];\nconst BASE_SIZE = 100;\nlet bombs = [];\nlet titleText;\nlet numTiles = 0;\nlet wpTileData = [];\nlet dataLoaded = false;\nlet tilesInitialized = false;\n\n\/\/ Nutzt direkt die vom System oder Elementor bereitgestellten Schriften\nconst FONT_REGULAR = 'Lausanne-300, Helvetica, Arial, sans-serif';\nconst FONT_ITALIC = 'Lausanne-300Italic, Helvetica, Arial, sans-serif';\n\nfunction preload() {\n  \/\/ preload bleibt komplett LEER. Dadurch startet p5.js ohne Millisekunden Verz\u00f6gerung sofort!\n}\n\nfunction setup() {\n  let canvas = createCanvas(windowWidth, windowHeight);\n  canvas.parent('p5-canvas-container');\n  rectMode(CENTER);\n  \n  \/\/ Titel sofort erstellen\n  titleText = new TitleText('Robert Overkamp', 0, 0, FONT_ITALIC, 50);\n\n  \/\/ Der WordPress-Aufruf l\u00e4uft jetzt ASYNCHRON im Hintergrund, ohne die Seite zu blockieren\n  loadWordPressData();\n}\n\nfunction loadWordPressData() {\n  fetch('\/wp-json\/wp\/v2\/posts?per_page=8') \/\/ Auf 8 Kacheln reduziert f\u00fcr bessere Performance\n    .then(response => {\n      if (!response.ok) throw new Error('Netzwerk-Fehler');\n      return response.json();\n    })\n    .then(posts => {\n      if (!Array.isArray(posts)) throw new Error('Kein valides Array');\n\n      wpTileData = posts.map(post => {\n        let excerpt = '';\n        if (post.excerpt && post.excerpt.rendered) {\n          excerpt = post.excerpt.rendered.replace(\/<[^>]*>\/g, '').trim();\n          if (excerpt.length > 30) excerpt = excerpt.substring(0, 30) + '...';\n        }\n        return {\n          title: post.title.rendered ? post.title.rendered : 'Unbenannt',\n          subtitle: excerpt || 'Visuelle Studie',\n          url: post.link\n        };\n      });\n      \n      numTiles = wpTileData.length;\n      dataLoaded = true;\n    })\n    .catch(err => {\n      console.error(\"WordPress-API tr\u00e4ge, lade lokale Fallbacks:\", err);\n      \/\/ Fallback-Daten, falls der Server offline\/langsam ist\n      wpTileData = [\n        { title: 'Exploration', subtitle: 'Visuelle Studie', url: '#' },\n        { title: 'Struktur', subtitle: 'Geordnete Fl\u00e4che', url: '#' },\n        { title: 'Material', subtitle: 'Haptisches Spiel', url: '#' },\n        { title: 'Komposition', subtitle: 'Bildaufbau', url: '#' }\n      ];\n      numTiles = wpTileData.length;\n      dataLoaded = true;\n    });\n}\n\nfunction draw() {\n  background(255);\n  \n  \/\/ Statische Bodenlinie\n  push();\n  stroke(0);\n  strokeWeight(2);\n  line(0, height - 40, width, height - 40);\n  pop();\n\n  \/\/ Titel rendern (l\u00e4uft von Sekunde 1 an fl\u00fcssig)\n  titleText.update();\n  titleText.display();\n\n  \/\/ Sobald die WP-Daten im Hintergrund geladen wurden, initialisieren wir die Kacheln EINMALIG\n  if (dataLoaded && !tilesInitialized) {\n    for (let i = 0; i < numTiles; i++) {\n      let data = wpTileData[i];\n      let x = random(BASE_SIZE \/ 2, width - BASE_SIZE \/ 2);\n      let y = random(BASE_SIZE \/ 2, height - BASE_SIZE \/ 2);\n      tiles.push(new Tile(x, y, BASE_SIZE, data.title, data.subtitle, data.url));\n    }\n    tilesInitialized = true;\n  }\n\n  \/\/ Bomben-Logik\n  for (let i = bombs.length - 1; i >= 0; i--) {\n    bombs[i].update();\n    bombs[i].display();\n    if (bombs[i].done) bombs.splice(i, 1);\n  }\n\n  let overImage = false;\n  if (tilesInitialized) {\n    for (let tile of tiles) {\n      tile.update();\n      if (tile.isMouseOver()) overImage = true;\n    }\n    checkCollisions();\n    for (let tile of tiles) tile.display();\n  }\n\n  cursor(overImage ? HAND : ARROW);\n}\n\n\/\/ --- TARGET CLASSES (PERFORMANCE OPTIMIERT) ---\n\nclass Tile {\n  constructor(x, y, baseSize, title, subtitle, url) {\n    this.x = x; this.y = y; \n    this.baseSize = baseSize; this.size = baseSize; this.targetSize = baseSize;\n    this.title = title; this.subtitle = subtitle; this.url = url; \n    this.vx = random(-1, 1); this.vy = random(-1, 1);\n    this.isDragging = false; this.offsetX = 0; this.offsetY = 0;\n    this.prevMouseX = null; this.prevMouseY = null; this.dragVx = 0; this.dragVy = 0;\n  }\n\n  update() {\n    if (this.isMouseOver()) { this.targetSize = 140; } else { this.targetSize = this.baseSize; }\n    this.size += (this.targetSize - this.size) * 0.2;\n    let radius = this.size \/ 2;\n\n    if (this.isDragging) {\n      this.x = mouseX + this.offsetX; this.y = mouseY + this.offsetY;\n      if (this.prevMouseX !== null && this.prevMouseY !== null) {\n        this.dragVx = (mouseX - this.prevMouseX) * 1.2; this.dragVy = (mouseY - this.prevMouseY) * 1.2;\n      }\n      this.prevMouseX = mouseX; this.prevMouseY = mouseY;\n      this.vx = this.dragVx; this.vy = this.dragVy;\n    } else {\n      this.x += this.vx; this.y += this.vy; this.vx *= 0.98; this.vy *= 0.98; \/\/ Reibung leicht erh\u00f6ht f\u00fcr stabilere Physik\n    }\n\n    if (this.x - radius < 0) { this.x = radius; this.vx *= -0.6; }\n    if (this.x + radius > width) { this.x = width - radius; this.vx *= -0.6; }\n    if (this.y - radius < 0) { this.y = radius; this.vy *= -0.6; }\n    const bottomLimit = height - 40;\n    if (this.y + radius > bottomLimit) { this.y = bottomLimit - radius; this.vy *= -0.6; }\n  }\n\n  display() {\n    push();\n    fill(248);\n    stroke(0);\n    strokeWeight(1.5);\n    rect(this.x, this.y, this.size, this.size);\n    pop();\n\n    if (this.isMouseOver()) {\n      push();\n      textAlign(CENTER, TOP); fill(0); noStroke();\n      textFont(FONT_REGULAR); textSize(15);\n      text(this.title, this.x, this.y + this.size \/ 2 + 8);\n      textFont(FONT_ITALIC); textSize(13);\n      text(this.subtitle, this.x, this.y + this.size \/ 2 + 26);\n      pop();\n    }\n  }\n\n  isMouseOver() {\n    let radius = this.size \/ 2;\n    return mouseX >= this.x - radius && mouseX <= this.x + radius &&\n           mouseY >= this.y - radius && mouseY <= this.y + radius;\n  }\n}\n\nclass TitleText {\n  constructor(text, left, top, font, fontSize) {\n    this.text = text; this.font = font; this.fontSize = fontSize;\n    this.isDragging = false; this.isStatic = true; this.padding = 16;\n    this.x = left; this.y = top;\n    this.textLines = [];\n    this.needsRecalc = true; \/\/ Flag, um CPU-Abfragen zu minimieren\n  }\n\n  update() {\n    if (this.needsRecalc) {\n      this.maxW = width \/ 3;\n      textFont(this.font); textSize(this.fontSize);\n      this.textLines = this.wrapText(this.text, this.maxW - this.padding * 2);\n      let textW = 0;\n      for (let line of this.textLines) { textW = max(textW, textWidth(line)); }\n      this.lineHeight = textAscent() + textDescent() + 4;\n      this.blockW = textW + this.padding * 2;\n      this.blockH = (this.textLines.length * this.lineHeight) + this.padding * 2;\n      this.needsRecalc = false; \/\/ Wird nur bei Resize wieder getriggert\n    }\n    this.x = constrain(this.x, 0, width - this.blockW);\n    this.y = constrain(this.y, 0, height - this.blockH);\n  }\n\n  wrapText(txt, maxWidth) {\n    const words = txt.split(' '); const lines = []; let current = '';\n    for (let word of words) {\n      const test = current ? current + ' ' + word : word;\n      if (textWidth(test) <= maxWidth) { current = test; } \n      else { if (current) lines.push(current); current = word; }\n    }\n    if (current) lines.push(current);\n    return lines;\n  }\n\n  display() {\n    push();\n    textFont(this.font); textSize(this.fontSize); textAlign(LEFT, TOP); fill(0); noStroke();\n    let ty = this.y + this.padding;\n    for (let line of this.textLines) {\n      text(line, this.x + this.padding, ty);\n      ty += this.lineHeight;\n    }\n    pop();\n  }\n\n  isMouseOver() {\n    return mouseX >= this.x && mouseX <= this.x + this.blockW &&\n           mouseY >= this.y && mouseY <= this.y + this.blockH;\n  }\n}\n\nclass Bomb {\n  constructor(x, y, delay) {\n    this.x = x; this.y = y; this.delay = delay; this.created = millis(); this.done = false; this.exploded = false;\n  }\n  update() {\n    const elapsed = millis() - this.created;\n    if (!this.exploded && elapsed >= this.delay) { this.explode(); this.exploded = true; this.done = true; }\n  }\n  display() {\n    const elapsed = millis() - this.created;\n    const remaining = max(0, this.delay - elapsed);\n    push(); noFill(); stroke(0); strokeWeight(2); ellipse(this.x, this.y, 32); line(this.x, this.y - 12, this.x, this.y - 24);\n    fill(0); noStroke(); textAlign(CENTER, CENTER); textSize(12); text(nf(remaining \/ 1000, 1, 1), this.x, this.y); pop();\n  }\n  explode() {\n    const strength = 25; const explosionRadius = 400;\n    for (let tile of tiles) {\n      const dx = tile.x - this.x; const dy = tile.y - this.y; const dist = sqrt(dx * dx + dy * dy);\n      if (dist > explosionRadius) continue;\n      const force = strength * (1 - dist \/ explosionRadius) \/ explosionRadius;\n      tile.vx += dx * force * 2; tile.vy += dy * force * 2;\n      tile.isDragging = false;\n    }\n  }\n}\n\nfunction checkCollisions() {\n  if (!tilesInitialized) return;\n  for (let i = 0; i < tiles.length; i++) {\n    for (let j = i + 1; j < tiles.length; j++) { resolveCollision(tiles[i], tiles[j]); }\n  }\n  for (let tile of tiles) { resolveCollision(tile, titleText); }\n}\n\nfunction resolveCollision(a, b) {\n  let aLeft, aRight, aTop, aBottom, aCx, aCy, bLeft, bRight, bTop, bBottom, bCx, bCy;\n  if (a.size) {\n    let half = a.size \/ 2; aLeft = a.x - half; aRight = a.x + half; aTop = a.y - half; aBottom = a.y + half; aCx = a.x; aCy = a.y;\n  } else {\n    aLeft = a.x; aRight = a.x + a.blockW; aTop = a.y; aBottom = a.y + a.blockH; aCx = a.x + a.blockW \/ 2; aCy = a.y + a.blockH \/ 2;\n  }\n  if (b.size) {\n    let half = b.size \/ 2; bLeft = b.x - half; bRight = b.x + half; bTop = b.y - half; bBottom = b.y + half; bCx = b.x; bCy = b.y;\n  } else {\n    bLeft = b.x; bRight = b.x + b.blockW; bTop = b.y; bBottom = b.y + b.blockH; bCx = b.x + b.blockW \/ 2; bCy = b.y + b.blockH \/ 2;\n  }\n\n  if (aLeft < bRight && aRight > bLeft && aTop < bBottom && aBottom > bTop) {\n    let overlapX = min(aRight, bRight) - max(aLeft, bLeft); let overlapY = min(aBottom, bBottom) - max(aTop, bTop);\n    if (overlapX > 0 && overlapY > 0) {\n      let restitution = 0.6; let axisX = overlapX < overlapY;\n      if (axisX) {\n        let sep = overlapX \/ 2 + 0.1; let normal = aCx < bCx ? 1 : -1;\n        if (aCx < bCx) {\n          if (!a.isDragging && !a.isStatic) a.x -= sep; if (!b.isDragging && !b.isStatic) b.x += sep;\n        } else {\n          if (!a.isDragging && !a.isStatic) a.x += sep; if (!b.isDragging && !b.isStatic) b.x -= sep;\n        }\n        let aVx = a.vx || 0; let bVx = b.vx || 0; let relVel = (bVx - aVx) * normal;\n        if (relVel < 0) {\n          let impulse = -(1 + restitution) * relVel \/ 2;\n          if (!a.isDragging && !a.isStatic) a.vx -= impulse * normal; if (!b.isDragging && !b.isStatic) b.vx += impulse * normal;\n        }\n      } else {\n        let sep = overlapY \/ 2 + 0.1; let normal = aCy < bCy ? 1 : -1;\n        if (aCy < bCy) {\n          if (!a.isDragging && !a.isStatic) a.y -= sep; if (!b.isDragging && !b.isStatic) b.y += sep;\n        } else {\n          if (!a.isDragging && !a.isStatic) a.y += sep; if (!b.isDragging && !b.isStatic) b.y -= sep;\n        }\n        let aVy = a.vy || 0; let bVy = b.vy || 0; let relVel = (bVy - aVy) * normal;\n        if (relVel < 0) {\n          let impulse = -(1 + restitution) * relVel \/ 2;\n          if (!a.isDragging && !a.isStatic) a.vy -= impulse * normal; if (!b.isDragging && !b.isStatic) b.vy += impulse * normal;\n        }\n      }\n    }\n  }\n}\n\nfunction mousePressed() {\n  if (!tilesInitialized) return;\n  let clickedTile = false;\n  for (let i = tiles.length - 1; i >= 0; i--) {\n    if (tiles[i].isMouseOver()) {\n      clickedTile = true; tiles[i].isDragging = true;\n      tiles[i].offsetX = tiles[i].x - mouseX; tiles[i].offsetY = tiles[i].y - mouseY;\n      tiles[i].prevMouseX = mouseX; tiles[i].prevMouseY = mouseY;\n      tiles[i].dragVx = 0; tiles[i].dragVy = 0;\n      let grabbed = tiles.splice(i, 1)[0]; tiles.push(grabbed); break;\n    }\n  }\n  if (!clickedTile) bombs.push(new Bomb(mouseX, mouseY, 300));\n}\n\nfunction mouseReleased() {\n  if (!tilesInitialized) return;\n  for (let tile of tiles) {\n    if (tile.isDragging) {\n      tile.isDragging = false; tile.vx = tile.dragVx * 1.2; tile.vy = tile.dragVy * 1.2;\n      if (abs(tile.dragVx) < 0.5 && abs(tile.dragVy) < 0.5 && tile.url && tile.url !== '#') {\n         window.location.href = tile.url; \n      }\n      tile.prevMouseX = null; tile.prevMouseY = null;\n    }\n  }\n}\n\nfunction windowResized() {\n  resizeCanvas(windowWidth, windowHeight);\n  if (titleText) titleText.needsRecalc = true;\n  if (tilesInitialized) {\n    for (let t of tiles) {\n      let radius = t.size \/ 2;\n      t.x = constrain(t.x, radius, width - radius); t.y = constrain(t.y, radius, height - radius);\n    }\n  }\n}\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-442","page","type-page","status-publish","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/robertoverkamp.com\/index.php?rest_route=\/wp\/v2\/pages\/442","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/robertoverkamp.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/robertoverkamp.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/robertoverkamp.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/robertoverkamp.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=442"}],"version-history":[{"count":43,"href":"https:\/\/robertoverkamp.com\/index.php?rest_route=\/wp\/v2\/pages\/442\/revisions"}],"predecessor-version":[{"id":492,"href":"https:\/\/robertoverkamp.com\/index.php?rest_route=\/wp\/v2\/pages\/442\/revisions\/492"}],"wp:attachment":[{"href":"https:\/\/robertoverkamp.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=442"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}