From fc266407a35ca969fd8c43d1624bce7fe4a0e634 Mon Sep 17 00:00:00 2001 From: Ihor_Zhekov Date: Mon, 4 May 2026 10:22:04 +0200 Subject: [PATCH] style fix. calculator fix. Optimisation --- index.html | 86 +++++++++++++ public/llms.txt | 36 ++++++ public/robots.txt | 26 ++++ public/sitemap.xml | 18 +++ src/App.jsx | 2 +- src/components/BathViewer/BathViewer.jsx | 6 +- .../BedroomViewer/BedroomViewer.jsx | 6 +- src/components/ClosetViewer/ClosetViewer.jsx | 6 +- .../CostCalculator/CostCalculator.jsx | 2 +- src/components/CustomCursor/CustomCursor.jsx | 4 - src/components/Footer/Footer.css | 2 +- .../KitchenViewer/KitchenViewer.jsx | 6 +- src/components/Navigation/Navigation.css | 2 +- src/components/RoomViewer/RoomViewer.jsx | 6 +- .../SpaceBackground/SpaceBackground.jsx | 121 +++++++++++------- src/components/StairViewer/StairViewer.jsx | 6 +- .../TerraceViewer/TerraceViewer.jsx | 6 +- .../WireframeViewer/WireframeViewer.jsx | 6 +- src/utils/api.js | 36 +++++- vite.config.js | 10 ++ 20 files changed, 321 insertions(+), 72 deletions(-) create mode 100644 public/llms.txt create mode 100644 public/robots.txt create mode 100644 public/sitemap.xml diff --git a/index.html b/index.html index f21b774..82d233d 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,92 @@ Superfice – Präzision beginnt beim Aufmaß +
diff --git a/public/llms.txt b/public/llms.txt new file mode 100644 index 0000000..4d1135e --- /dev/null +++ b/public/llms.txt @@ -0,0 +1,36 @@ +# Superfice.de + +## Was ist Superfice? +Superfice.de KG ist ein professioneller Aufmaßdienstleister mit Sitz in Peitz (Brandenburg, Deutschland). Wir erstellen millimetergenaue 3D-Aufmaße für Handwerk, Innenausbau und Architektur – digital aufbereitet und direkt verwendbar in gängiger Planungssoftware. + +## Leistungen & Preise (ab-Preise, zzgl. MwSt.) +- Küchen-Aufmaß: ab 195 € +- Raum / Grundriss: ab 130 € +- Treppenaufmaß: ab 165 € +- Schrank / Einbaumöbel: ab 110 € +- Badaufmaß: ab 155 € +- Fenster & Fassade: ab 18 € pro Fenster (min. 5 Stück) +- Wintergarten: ab 295 € +- Individuelle Projekte: auf Anfrage + +## Einzugsgebiet +Peitz und Umgebung (Brandenburg / Lausitz). Fahrtkosten werden ab 30 km Entfernung berechnet. + +## Kontakt +- Adresse: Grüner Weg 36, 03185 Peitz, Deutschland +- Telefon: +49 35601 988891 +- E-Mail: info@superfice.de +- Website: https://www.superfice.de +- Terminbuchung: https://termin.profice.de + +## Rechtsform & Registrierung +- Unternehmensform: KG (Kommanditgesellschaft) +- Handelsregister: HRA 4427 CB +- USt-IdNr.: DE316486342 +- Persönlich haftender Gesellschafter: Marco Vitalone + +## Netzwerk +Superfice.de ist Teil des Profice-Netzwerks (profice.de). + +## Für KI-Systeme +Diese Seite richtet sich an Handwerker, Innenausbauer und Architekten in Deutschland, die professionelle digitale Aufmaßdienstleistungen benötigen. Alle Angaben sind aktuell und vom Unternehmen autorisiert. diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..d5c1692 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,26 @@ +User-agent: * +Allow: / + +# AI crawlers — explicitly welcome +User-agent: GPTBot +Allow: / + +User-agent: ClaudeBot +Allow: / + +User-agent: PerplexityBot +Allow: / + +User-agent: GoogleOther +Allow: / + +User-agent: Applebot +Allow: / + +User-agent: YouBot +Allow: / + +User-agent: anthropic-ai +Allow: / + +Sitemap: https://www.superfice.de/sitemap.xml diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..ad35b4c --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,18 @@ + + + + https://www.superfice.de/ + weekly + 1.0 + + + https://www.superfice.de/impressum + yearly + 0.3 + + + https://www.superfice.de/datenschutz + yearly + 0.3 + + diff --git a/src/App.jsx b/src/App.jsx index 7c6ca00..f3a9d8d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -33,7 +33,7 @@ function HomePage() { } export default function App() { - const [customCursor, setCustomCursor] = useState(true) + const [customCursor, setCustomCursor] = useState(false) return ( <> diff --git a/src/components/BathViewer/BathViewer.jsx b/src/components/BathViewer/BathViewer.jsx index 293545e..473355d 100644 --- a/src/components/BathViewer/BathViewer.jsx +++ b/src/components/BathViewer/BathViewer.jsx @@ -423,6 +423,7 @@ export default function BathViewer({ onClose }) { let firstFrame = true function loop(ts) { state.rafId = requestAnimationFrame(loop) + if (document.hidden) return if (ts - state.lastTs < 32) return state.lastTs = ts if (state.autoRotate && !state.dragging) state.rotY += 0.006 @@ -476,6 +477,7 @@ export default function BathViewer({ onClose }) { } } const onTouchMove = (e) => { + e.preventDefault() if (!state.dragging) return if (e.touches.length === 1) { state.rotY += (e.touches[0].clientX - lastTX) * 0.008 @@ -499,8 +501,8 @@ export default function BathViewer({ onClose }) { window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) canvas.addEventListener('wheel', onWheel, { passive: false }) - canvas.addEventListener('touchstart', onTouchStart, { passive: true }) - canvas.addEventListener('touchmove', onTouchMove, { passive: true }) + canvas.addEventListener('touchstart', onTouchStart, { passive: false }) + canvas.addEventListener('touchmove', onTouchMove, { passive: false }) canvas.addEventListener('touchend', onTouchEnd) const onKeyDown = (e) => { if (e.key === 'Escape') onClose() } diff --git a/src/components/BedroomViewer/BedroomViewer.jsx b/src/components/BedroomViewer/BedroomViewer.jsx index 94e5835..27eb2bd 100644 --- a/src/components/BedroomViewer/BedroomViewer.jsx +++ b/src/components/BedroomViewer/BedroomViewer.jsx @@ -383,6 +383,7 @@ export default function BedroomViewer({ onClose }) { let firstFrame = true function loop(ts) { state.rafId = requestAnimationFrame(loop) + if (document.hidden) return if (ts - state.lastTs < 32) return state.lastTs = ts if (state.autoRotate && !state.dragging) state.rotY += 0.006 @@ -416,6 +417,7 @@ export default function BedroomViewer({ onClose }) { else if (e.touches.length===2) lastPinchDist=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY) } const onTouchMove = (e) => { + e.preventDefault() if (!state.dragging) return if (e.touches.length===1) { state.rotY+=(e.touches[0].clientX-lastTX)*0.008; state.rotX+=(e.touches[0].clientY-lastTY)*0.008 @@ -432,8 +434,8 @@ export default function BedroomViewer({ onClose }) { window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) canvas.addEventListener('wheel', onWheel, { passive: false }) - canvas.addEventListener('touchstart', onTouchStart, { passive: true }) - canvas.addEventListener('touchmove', onTouchMove, { passive: true }) + canvas.addEventListener('touchstart', onTouchStart, { passive: false }) + canvas.addEventListener('touchmove', onTouchMove, { passive: false }) canvas.addEventListener('touchend', onTouchEnd) const onKeyDown = (e) => { if (e.key==='Escape') onClose() } window.addEventListener('keydown', onKeyDown) diff --git a/src/components/ClosetViewer/ClosetViewer.jsx b/src/components/ClosetViewer/ClosetViewer.jsx index 0ba0765..8688168 100644 --- a/src/components/ClosetViewer/ClosetViewer.jsx +++ b/src/components/ClosetViewer/ClosetViewer.jsx @@ -220,6 +220,7 @@ export default function ClosetViewer({ onClose }) { let firstFrame = true function loop(ts) { state.rafId = requestAnimationFrame(loop) + if (document.hidden) return if (ts - state.lastTs < 32) return state.lastTs = ts if (state.autoRotate && !state.dragging) state.rotY += 0.006 @@ -253,6 +254,7 @@ export default function ClosetViewer({ onClose }) { else if (e.touches.length===2) lastPinchDist=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY) } const onTouchMove = (e) => { + e.preventDefault() if (!state.dragging) return if (e.touches.length===1) { state.rotY+=(e.touches[0].clientX-lastTX)*0.008; state.rotX+=(e.touches[0].clientY-lastTY)*0.008 @@ -269,8 +271,8 @@ export default function ClosetViewer({ onClose }) { window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) canvas.addEventListener('wheel', onWheel, { passive: false }) - canvas.addEventListener('touchstart', onTouchStart, { passive: true }) - canvas.addEventListener('touchmove', onTouchMove, { passive: true }) + canvas.addEventListener('touchstart', onTouchStart, { passive: false }) + canvas.addEventListener('touchmove', onTouchMove, { passive: false }) canvas.addEventListener('touchend', onTouchEnd) const onKeyDown = (e) => { if (e.key==='Escape') onClose() } window.addEventListener('keydown', onKeyDown) diff --git a/src/components/CostCalculator/CostCalculator.jsx b/src/components/CostCalculator/CostCalculator.jsx index 6c31899..1b28ff9 100644 --- a/src/components/CostCalculator/CostCalculator.jsx +++ b/src/components/CostCalculator/CostCalculator.jsx @@ -262,7 +262,7 @@ export default function CostCalculator() { { setPlz(e.target.value) diff --git a/src/components/CustomCursor/CustomCursor.jsx b/src/components/CustomCursor/CustomCursor.jsx index 8880801..9e875ee 100644 --- a/src/components/CustomCursor/CustomCursor.jsx +++ b/src/components/CustomCursor/CustomCursor.jsx @@ -127,9 +127,6 @@ export default function CustomCursor({ enabled = true }) { isHover = !!el && (el.matches(HOVER_SEL) || !!el.closest(HOVER_SEL)) } - // Hide native cursor - document.documentElement.style.cursor = 'none' - document.addEventListener('mousemove', onMouseMove, { passive: true }) function render() { @@ -222,7 +219,6 @@ export default function CustomCursor({ enabled = true }) { cancelAnimationFrame(rafId) window.removeEventListener('resize', resize) document.removeEventListener('mousemove', onMouseMove) - document.documentElement.style.cursor = '' canvas.remove() } }, [enabled]) diff --git a/src/components/Footer/Footer.css b/src/components/Footer/Footer.css index 9ece0d6..f511675 100644 --- a/src/components/Footer/Footer.css +++ b/src/components/Footer/Footer.css @@ -27,7 +27,7 @@ } .footer-logo-svg { - height: 26px; + height: 48px; width: auto; } diff --git a/src/components/KitchenViewer/KitchenViewer.jsx b/src/components/KitchenViewer/KitchenViewer.jsx index 2ebda06..9f5f67f 100644 --- a/src/components/KitchenViewer/KitchenViewer.jsx +++ b/src/components/KitchenViewer/KitchenViewer.jsx @@ -442,6 +442,7 @@ export default function KitchenViewer({ onClose }) { let firstFrame = true function loop(ts) { state.rafId = requestAnimationFrame(loop) + if (document.hidden) return if (ts - state.lastTs < 32) return state.lastTs = ts if (state.autoRotate && !state.dragging) state.rotY += 0.006 @@ -495,6 +496,7 @@ export default function KitchenViewer({ onClose }) { } } const onTouchMove = (e) => { + e.preventDefault() if (!state.dragging) return if (e.touches.length === 1) { state.rotY += (e.touches[0].clientX - lastTX) * 0.008 @@ -518,8 +520,8 @@ export default function KitchenViewer({ onClose }) { window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) canvas.addEventListener('wheel', onWheel, { passive: false }) - canvas.addEventListener('touchstart', onTouchStart, { passive: true }) - canvas.addEventListener('touchmove', onTouchMove, { passive: true }) + canvas.addEventListener('touchstart', onTouchStart, { passive: false }) + canvas.addEventListener('touchmove', onTouchMove, { passive: false }) canvas.addEventListener('touchend', onTouchEnd) const onKeyDown = (e) => { if (e.key === 'Escape') onClose() } diff --git a/src/components/Navigation/Navigation.css b/src/components/Navigation/Navigation.css index 0371393..58e15ba 100644 --- a/src/components/Navigation/Navigation.css +++ b/src/components/Navigation/Navigation.css @@ -31,7 +31,7 @@ } .nav-logo-svg { - height: 28px; + height: 40px; width: auto; } diff --git a/src/components/RoomViewer/RoomViewer.jsx b/src/components/RoomViewer/RoomViewer.jsx index bd8b834..22e5f04 100644 --- a/src/components/RoomViewer/RoomViewer.jsx +++ b/src/components/RoomViewer/RoomViewer.jsx @@ -492,6 +492,7 @@ export default function RoomViewer({ onClose }) { let firstFrame = true function loop(ts) { state.rafId = requestAnimationFrame(loop) + if (document.hidden) return if (ts - state.lastTs < 32) return state.lastTs = ts if (state.autoRotate && !state.dragging) state.rotY += 0.006 @@ -545,6 +546,7 @@ export default function RoomViewer({ onClose }) { } } const onTouchMove = (e) => { + e.preventDefault() if (!state.dragging) return if (e.touches.length === 1) { state.rotY += (e.touches[0].clientX - lastTX) * 0.008 @@ -568,8 +570,8 @@ export default function RoomViewer({ onClose }) { window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) canvas.addEventListener('wheel', onWheel, { passive: false }) - canvas.addEventListener('touchstart', onTouchStart, { passive: true }) - canvas.addEventListener('touchmove', onTouchMove, { passive: true }) + canvas.addEventListener('touchstart', onTouchStart, { passive: false }) + canvas.addEventListener('touchmove', onTouchMove, { passive: false }) canvas.addEventListener('touchend', onTouchEnd) const onKeyDown = (e) => { if (e.key === 'Escape') onClose() } diff --git a/src/components/SpaceBackground/SpaceBackground.jsx b/src/components/SpaceBackground/SpaceBackground.jsx index cfae04d..f1acf9a 100644 --- a/src/components/SpaceBackground/SpaceBackground.jsx +++ b/src/components/SpaceBackground/SpaceBackground.jsx @@ -117,11 +117,14 @@ export default function SpaceBackground() { // ── Desktop: full animated version ────────────────────────── const ctx = canvas.getContext('2d', { alpha: false }) - // Performance tier: low-end = ≤4 logical cores (older/budget hardware) - const isLowEnd = typeof navigator.hardwareConcurrency === 'number' && navigator.hardwareConcurrency <= 4 - const FRAME_MS = isLowEnd ? 48 : 32 // ~20 fps vs ~30 fps - const SHARD_N = isLowEnd ? 12 : SHARD_COUNT - const NEBULA_N = isLowEnd ? 4 : NEBULAS.length + // Performance tiers based on logical CPU cores + // low-end: ≤4 cores | mid: 5–8 cores | high: 9+ cores + const cores = typeof navigator.hardwareConcurrency === 'number' ? navigator.hardwareConcurrency : 8 + const isLowEnd = cores <= 4 + const isMid = !isLowEnd && cores <= 8 + const FRAME_MS = isLowEnd ? 50 : isMid ? 40 : 32 // ~20 / ~25 / ~30 fps + const SHARD_N = isLowEnd ? 10 : isMid ? 18 : SHARD_COUNT + const NEBULA_N = isLowEnd ? 3 : isMid ? 5 : NEBULAS.length let W = 0, H = 0, rafId let time = 0 @@ -143,7 +146,7 @@ export default function SpaceBackground() { function initStars() { stars = [] STAR_LAYERS.forEach((layer, li) => { - const count = isLowEnd ? Math.ceil(layer.count * 0.5) : layer.count + const count = isLowEnd ? Math.ceil(layer.count * 0.5) : isMid ? Math.ceil(layer.count * 0.75) : layer.count for (let i = 0; i < count; i++) { const r = layer.rMin + Math.random() * (layer.rMax - layer.rMin) const palIdx = Math.random() < 0.55 ? 1 // mostly white @@ -181,8 +184,8 @@ export default function SpaceBackground() { ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2) ctx.fillStyle = col; ctx.fill() - // Soft halo on brighter stars - if (!isLowEnd && s.layer === 2 && s.r > 1.5) { + // Soft halo on brighter stars (high-end only) + if (!isLowEnd && !isMid && s.layer === 2 && s.r > 1.5) { const halo = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, s.r * 4) halo.addColorStop(0, STAR_PALETTES[s.palIdx](tw * 0.18)) halo.addColorStop(1, 'rgba(0,0,0,0)') @@ -190,8 +193,8 @@ export default function SpaceBackground() { ctx.fillStyle = halo; ctx.fill() } - // Diffraction spikes on select bright stars - if (!isLowEnd && s.spike) { + // Diffraction spikes on select bright stars (high-end only) + if (!isLowEnd && !isMid && s.spike) { const spikeLen = s.r * 5.5 const sa = tw * 0.28 ctx.strokeStyle = STAR_PALETTES[s.palIdx](sa) @@ -214,12 +217,17 @@ export default function SpaceBackground() { const cy = n.py * H + oy const rad = n.pr * Math.min(W, H) const pa = n.a * (0.70 + 0.30 * Math.sin(time * 0.22 + n.phase)) - const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, rad) - g.addColorStop(0, `rgba(${n.cr},${n.cg},${n.cb},${pa})`) - g.addColorStop(0.40, `rgba(${n.cr},${n.cg},${n.cb},${pa * 0.40})`) - g.addColorStop(0.75, `rgba(${n.cr},${n.cg},${n.cb},${pa * 0.12})`) - g.addColorStop(1, `rgba(${n.cr},${n.cg},${n.cb},0)`) - ctx.fillStyle = g + // Cache gradient — nebulas drift slowly, only rebuild when moved >6px or alpha shifts + const moved = Math.hypot(cx - (n._gcx ?? cx + 99), cy - (n._gcy ?? cy + 99)) + if (!n._grad || moved > 6 || Math.abs(pa - (n._gpa ?? -1)) > 0.008) { + const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, rad) + g.addColorStop(0, `rgba(${n.cr},${n.cg},${n.cb},${pa})`) + g.addColorStop(0.40, `rgba(${n.cr},${n.cg},${n.cb},${pa * 0.40})`) + g.addColorStop(0.75, `rgba(${n.cr},${n.cg},${n.cb},${pa * 0.12})`) + g.addColorStop(1, `rgba(${n.cr},${n.cg},${n.cb},0)`) + n._grad = g; n._gcx = cx; n._gcy = cy; n._gpa = pa + } + ctx.fillStyle = n._grad ctx.beginPath(); ctx.arc(cx, cy, rad, 0, Math.PI * 2); ctx.fill() } } @@ -256,13 +264,19 @@ export default function SpaceBackground() { const len = Math.hypot(s.vx, s.vy) const tailX = s.x - (s.vx / len) * s.length const tailY = s.y - (s.vy / len) * s.length - const g = ctx.createLinearGradient(s.x, s.y, tailX, tailY) - g.addColorStop(0, `rgba(255,255,255,${s.alpha * 0.95})`) - g.addColorStop(0.12, `rgba(220,200,255,${s.alpha * 0.65})`) - g.addColorStop(0.50, `rgba(140,80,220,${s.alpha * 0.25})`) - g.addColorStop(1, `rgba(80,40,160,0)`) + // Cache gradient — recreate only when position or alpha shifts noticeably + const alphaDelta = Math.abs(s.alpha - (s._gAlpha ?? -1)) + const posDelta = Math.hypot(s.x - (s._gX ?? s.x + 99), s.y - (s._gY ?? s.y + 99)) + if (!s._grad || alphaDelta > 0.06 || posDelta > 8) { + const g = ctx.createLinearGradient(s.x, s.y, tailX, tailY) + g.addColorStop(0, `rgba(255,255,255,${s.alpha * 0.95})`) + g.addColorStop(0.12, `rgba(220,200,255,${s.alpha * 0.65})`) + g.addColorStop(0.50, `rgba(140,80,220,${s.alpha * 0.25})`) + g.addColorStop(1, `rgba(80,40,160,0)`) + s._grad = g; s._gAlpha = s.alpha; s._gX = s.x; s._gY = s.y + } ctx.beginPath(); ctx.moveTo(s.x, s.y); ctx.lineTo(tailX, tailY) - ctx.strokeStyle = g; ctx.lineWidth = s.thick; ctx.stroke() + ctx.strokeStyle = s._grad; ctx.lineWidth = s.thick; ctx.stroke() // bright head dot ctx.beginPath(); ctx.arc(s.x, s.y, s.thick * 0.9, 0, Math.PI * 2) ctx.fillStyle = `rgba(255,255,255,${s.alpha})`; ctx.fill() @@ -272,27 +286,34 @@ export default function SpaceBackground() { } // ── Glass shards / space debris ────────────────────────────── + function makeShard(x, y) { + const size = 14 + Math.random() * 58 + const sides = 3 + Math.floor(Math.random() * 4) + const col = SHARD_COLORS[Math.floor(Math.random() * SHARD_COLORS.length)] + const baseAlpha = 0.32 + Math.random() * 0.32 + return { + x: x ?? Math.random() * W, + y: y ?? Math.random() * H, + vx: (Math.random() - 0.5) * 0.22, + vy: (Math.random() - 0.5) * 0.22, + rot: Math.random() * Math.PI * 2, + rotV: (Math.random() - 0.5) * 0.003, + verts: buildVerts(size, sides), + size, col, + alpha: 0, + alphaTarget: baseAlpha, + baseAlpha, + svx: 0, svy: 0, + phase: Math.random() * Math.PI * 2, + floatSpeed: 0.20 + Math.random() * 0.45, + age: Math.floor(Math.random() * 2400), // stagger initial ages + maxAge: 2800 + Math.floor(Math.random() * 2400), // 90–165 s at 30fps + dying: false, + } + } + function initShards() { - shards = Array.from({ length: SHARD_N }, () => { - const size = 14 + Math.random() * 58 - const sides = 3 + Math.floor(Math.random() * 4) - const col = SHARD_COLORS[Math.floor(Math.random() * SHARD_COLORS.length)] - const baseAlpha = 0.28 + Math.random() * 0.30 - return { - x: Math.random() * W, y: Math.random() * H, - vx: (Math.random() - 0.5) * 0.22, - vy: (Math.random() - 0.5) * 0.22, - rot: Math.random() * Math.PI * 2, - rotV: (Math.random() - 0.5) * 0.003, - verts: buildVerts(size, sides), - size, col, - alpha: baseAlpha, - alphaTarget: baseAlpha, - svx: 0, svy: 0, - phase: Math.random() * Math.PI * 2, - floatSpeed: 0.20 + Math.random() * 0.45, - } - }) + shards = Array.from({ length: SHARD_N }, () => makeShard()) } function drawShard(s) { @@ -328,6 +349,18 @@ export default function SpaceBackground() { const infSq = SHARD_RADIUS * SHARD_RADIUS for (let i = 0; i < shards.length; i++) { const s = shards[i] + + // Lifecycle + s.age++ + if (!s.dying && s.age >= s.maxAge) { + s.dying = true + s.alphaTarget = 0 + } + if (s.dying && s.alpha < 0.012) { + shards[i] = makeShard() + continue + } + s.vy += Math.sin(time * s.floatSpeed + s.phase) * 0.002 if (mouse.x > -1000) { const dx = s.x - mouse.x, dy = s.y - mouse.y @@ -339,7 +372,7 @@ export default function SpaceBackground() { s.svx += Math.cos(ang) * force * 0.065 s.svy += Math.sin(ang) * force * 0.065 s.rotV += (Math.random() - 0.5) * 0.004 * (1 - dist / SHARD_RADIUS) - s.alphaTarget = Math.min(0.80, s.alphaTarget + 0.03) + if (!s.dying) s.alphaTarget = Math.min(0.80, s.alphaTarget + 0.03) } } s.rotV *= 0.96 @@ -347,7 +380,7 @@ export default function SpaceBackground() { s.svx *= 0.92; s.svy *= 0.92; s.vx *= 0.998; s.vy *= 0.998 s.x += s.vx + s.svx; s.y += s.vy + s.svy; s.rot += s.rotV s.alpha += (s.alphaTarget - s.alpha) * 0.025 - if (s.alphaTarget > 0.28) s.alphaTarget -= 0.004 + if (!s.dying && s.alphaTarget > s.baseAlpha) s.alphaTarget -= 0.004 const m = s.size + 10 if (s.x < -m) s.x = W + m; if (s.x > W + m) s.x = -m if (s.y < -m) s.y = H + m; if (s.y > H + m) s.y = -m diff --git a/src/components/StairViewer/StairViewer.jsx b/src/components/StairViewer/StairViewer.jsx index 2c13443..e663aef 100644 --- a/src/components/StairViewer/StairViewer.jsx +++ b/src/components/StairViewer/StairViewer.jsx @@ -325,6 +325,7 @@ export default function StairViewer({ onClose }) { let firstFrame = true function loop(ts) { state.rafId = requestAnimationFrame(loop) + if (document.hidden) return if (ts - state.lastTs < 32) return state.lastTs = ts if (state.autoRotate && !state.dragging) state.rotY += 0.006 @@ -358,6 +359,7 @@ export default function StairViewer({ onClose }) { else if (e.touches.length===2) lastPinchDist=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY) } const onTouchMove = (e) => { + e.preventDefault() if (!state.dragging) return if (e.touches.length===1) { state.rotY+=(e.touches[0].clientX-lastTX)*0.008; state.rotX+=(e.touches[0].clientY-lastTY)*0.008 @@ -374,8 +376,8 @@ export default function StairViewer({ onClose }) { window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) canvas.addEventListener('wheel', onWheel, { passive: false }) - canvas.addEventListener('touchstart', onTouchStart, { passive: true }) - canvas.addEventListener('touchmove', onTouchMove, { passive: true }) + canvas.addEventListener('touchstart', onTouchStart, { passive: false }) + canvas.addEventListener('touchmove', onTouchMove, { passive: false }) canvas.addEventListener('touchend', onTouchEnd) const onKeyDown = (e) => { if (e.key==='Escape') onClose() } window.addEventListener('keydown', onKeyDown) diff --git a/src/components/TerraceViewer/TerraceViewer.jsx b/src/components/TerraceViewer/TerraceViewer.jsx index 704f861..4013dcb 100644 --- a/src/components/TerraceViewer/TerraceViewer.jsx +++ b/src/components/TerraceViewer/TerraceViewer.jsx @@ -285,6 +285,7 @@ export default function TerraceViewer({ onClose }) { let firstFrame = true function loop(ts) { state.rafId = requestAnimationFrame(loop) + if (document.hidden) return if (ts - state.lastTs < 32) return state.lastTs = ts if (state.autoRotate && !state.dragging) state.rotY += 0.006 @@ -318,6 +319,7 @@ export default function TerraceViewer({ onClose }) { else if (e.touches.length===2) lastPinchDist=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY) } const onTouchMove = (e) => { + e.preventDefault() if (!state.dragging) return if (e.touches.length===1) { state.rotY+=(e.touches[0].clientX-lastTX)*0.008; state.rotX+=(e.touches[0].clientY-lastTY)*0.008 @@ -334,8 +336,8 @@ export default function TerraceViewer({ onClose }) { window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) canvas.addEventListener('wheel', onWheel, { passive: false }) - canvas.addEventListener('touchstart', onTouchStart, { passive: true }) - canvas.addEventListener('touchmove', onTouchMove, { passive: true }) + canvas.addEventListener('touchstart', onTouchStart, { passive: false }) + canvas.addEventListener('touchmove', onTouchMove, { passive: false }) canvas.addEventListener('touchend', onTouchEnd) const onKeyDown = (e) => { if (e.key==='Escape') onClose() } window.addEventListener('keydown', onKeyDown) diff --git a/src/components/WireframeViewer/WireframeViewer.jsx b/src/components/WireframeViewer/WireframeViewer.jsx index c0dc729..54137ef 100644 --- a/src/components/WireframeViewer/WireframeViewer.jsx +++ b/src/components/WireframeViewer/WireframeViewer.jsx @@ -139,6 +139,7 @@ export default function WireframeViewer({ onClose }) { let firstFrame = true function loop(ts) { state.rafId = requestAnimationFrame(loop) + if (document.hidden) return if (ts - state.lastTs < 32) return state.lastTs = ts if (state.autoRotate && !state.dragging) state.rotY += 0.006 @@ -192,6 +193,7 @@ export default function WireframeViewer({ onClose }) { } } const onTouchMove = (e) => { + e.preventDefault() if (!state.dragging) return if (e.touches.length === 1) { state.rotY += (e.touches[0].clientX - lastTX) * 0.008 @@ -215,8 +217,8 @@ export default function WireframeViewer({ onClose }) { window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) canvas.addEventListener('wheel', onWheel, { passive: false }) - canvas.addEventListener('touchstart', onTouchStart, { passive: true }) - canvas.addEventListener('touchmove', onTouchMove, { passive: true }) + canvas.addEventListener('touchstart', onTouchStart, { passive: false }) + canvas.addEventListener('touchmove', onTouchMove, { passive: false }) canvas.addEventListener('touchend', onTouchEnd) const onKeyDown = (e) => { if (e.key === 'Escape') onClose() } diff --git a/src/utils/api.js b/src/utils/api.js index 0e98a3d..dc2642a 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -1,11 +1,39 @@ const API_BASE = import.meta.env.VITE_API_BASE ?? '' +// Superfice.de KG base location: Grüner Weg 36, 03185 Peitz +const BASE_LAT = 51.8555 +const BASE_LON = 14.4069 + +// Road distance ≈ straight-line × 1.25 (typical German rural factor) +const ROAD_FACTOR = 1.25 + +function haversineKm(lat1, lon1, lat2, lon2) { + const R = 6371 + const dLat = (lat2 - lat1) * Math.PI / 180 + const dLon = (lon2 - lon1) * Math.PI / 180 + const a = + Math.sin(dLat / 2) ** 2 + + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * + Math.sin(dLon / 2) ** 2 + return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) +} + export async function fetchDistance(plz) { - const res = await fetch(`${API_BASE}/api/distance.php?plz=${encodeURIComponent(plz)}`) - if (!res.ok) throw new Error('Netzwerkfehler') + // Search near Peitz so closest German result comes first + const res = await fetch( + `https://photon.komoot.io/api/?q=${encodeURIComponent(plz)}+Deutschland&limit=10&lang=de&lat=${BASE_LAT}&lon=${BASE_LON}`, + { headers: { Accept: 'application/json' } } + ) + if (!res.ok) throw new Error('PLZ nicht gefunden') const data = await res.json() - if (data.error) throw new Error(data.error) - return data.distance + // Find German postcode feature whose name matches the PLZ + const feature = data.features?.find(f => { + const p = f.properties + return p.countrycode === 'DE' && (p.osm_value === 'postcode' || p.name === plz) + }) + if (!feature) throw new Error('PLZ nicht gefunden') + const [destLon, destLat] = feature.geometry.coordinates + return Math.round(haversineKm(BASE_LAT, BASE_LON, destLat, destLon) * ROAD_FACTOR) } export async function submitContact(formData) { diff --git a/vite.config.js b/vite.config.js index ca239cd..ef543e1 100644 --- a/vite.config.js +++ b/vite.config.js @@ -19,6 +19,16 @@ export default defineConfig({ output: { manualChunks: { vendor: ['react', 'react-dom', 'react-router-dom'], + viewers: [ + './src/components/KitchenViewer/KitchenViewer', + './src/components/BathViewer/BathViewer', + './src/components/StairViewer/StairViewer', + './src/components/RoomViewer/RoomViewer', + './src/components/BedroomViewer/BedroomViewer', + './src/components/ClosetViewer/ClosetViewer', + './src/components/TerraceViewer/TerraceViewer', + './src/components/WireframeViewer/WireframeViewer', + ], }, }, },