From e33c509856bfa9d043092dc52722d6cabe17584c Mon Sep 17 00:00:00 2001 From: T-A-H-prog Date: Thu, 10 Sep 2026 21:07:05 +0200 Subject: [PATCH] iframe --- src/App.vue | 1 - src/components/big/PcBooks.vue | 449 ++++++++++++++++++++----- src/components/computer/OsWebseite.vue | 376 +++++++++++++++++++++ src/components/slim/AboutMe.vue | 6 +- src/router/index.ts | 6 +- src/views/HomeBig.vue | 8 +- src/views/HomeSlime.vue | 4 +- 7 files changed, 752 insertions(+), 98 deletions(-) create mode 100644 src/components/computer/OsWebseite.vue diff --git a/src/App.vue b/src/App.vue index 950bb73..ecf7fed 100644 --- a/src/App.vue +++ b/src/App.vue @@ -25,7 +25,6 @@ body, body { font-family: Inter, system-ui, sans-serif; - background: #0f0f0f; color: white; overflow-x: hidden; cursor: diff --git a/src/components/big/PcBooks.vue b/src/components/big/PcBooks.vue index 1db2f1f..ee8f85d 100644 --- a/src/components/big/PcBooks.vue +++ b/src/components/big/PcBooks.vue @@ -2,62 +2,82 @@ import { onMounted, onUnmounted, ref } from "vue"; import * as THREE from "three"; import gsap from "gsap"; -import ScrollTrigger from "gsap/ScrollTrigger"; + +import OsWebseite from "../computer/OsWebseite.vue"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; +import { + CSS3DRenderer, + CSS3DObject, +} from "three/examples/jsm/renderers/CSS3DRenderer.js"; -import ski_video from "/vids/th.mp4"; - -import normal_texture from "../../assets/myself/terminalretro.jpg"; +defineOptions({ inheritAttrs: false }); const canvasRef = ref(null); -let renderer, scene, camera, model, controls, gsapCtx; +const cssRendererRef = ref(null); + +let renderer; +let cssRenderer; + +let scene; +let cssScene; + +let camera; +let model; +let controls; +let gsapCtx; + let raycaster; let mouse; -let screens; +let screenMesh; -const video_ski = document.createElement("video"); +let iframe; +let iframeObject; -video_ski.src = ski_video; -video_ski.loop = true; -video_ski.muted = true; +let computerMode = false; +let cameraAnimation = null; -function initThree(canvas) { +const originalCameraPosition = new THREE.Vector3(); +const originalControlsTarget = new THREE.Vector3(); + +function initThree(canvas, cssContainer) { renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true, }); - renderer.setPixelRatio(1); //TODO - + renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.outputColorSpace = THREE.SRGBColorSpace; + scene = new THREE.Scene(); + cssScene = new THREE.Scene(); + camera = new THREE.PerspectiveCamera(23, 1, 0.1, 140); - // const light = new THREE.DirectionalLight(0xffe0b0, 0.9); - // light.position.set(-1, 4, 5); - // scene.add(light); - controls = new OrbitControls(camera, renderer.domElement); + + controls.enableZoom = false; controls.enableDamping = true; - // controls.enabled = false; - - raycaster = new THREE.Raycaster(); - mouse = new THREE.Vector2(); - - // controls.minDistance = 5; - // controls.maxDistance = 5; controls.maxPolarAngle = Math.PI / 2; controls.minAzimuthAngle = -Math.PI / 2; controls.maxAzimuthAngle = Math.PI / 2; - scene.add(new THREE.AmbientLight(0xffffff, 0.02)); + raycaster = new THREE.Raycaster(); + mouse = new THREE.Vector2(); + + cssRenderer = new CSS3DRenderer(); + cssRenderer.domElement.className = "css-renderer"; + cssRenderer.domElement.style.position = "absolute"; + cssRenderer.domElement.style.inset = "0"; + + cssContainer.appendChild(cssRenderer.domElement); + + // scene.add(new THREE.AmbientLight(0xffffff, 0.02)); loadModel(); - gsap.ticker.add(render); } @@ -87,7 +107,15 @@ function loadModel() { // Kamera passend zum Objekt positionieren const distance = maxSize * 2; - screen = model.getObjectByName("fernseher1Bild"); + screenMesh = + model.getObjectByName("screen") ?? + model.getObjectByName("fernseher1Bild"); + + if (screenMesh) { + createComputerScreen(screenMesh); + } else { + console.warn("Kein Bildschirm-Mesh gefunden"); + } // camera.position.set(center.x, center.y, center.z + distance); @@ -106,11 +134,6 @@ function loadModel() { // OrbitControls auf das Objekt ausrichten controls.target.copy(center); controls.update(); - - console.log("Model geladen:", model); - console.log("Center:", center); - console.log("Size:", size); - const bulb = model.getObjectByName("light"); if (bulb) { @@ -130,6 +153,7 @@ function loadModel() { (progress) => { if (progress.total) { const percent = ((progress.loaded / progress.total) * 100).toFixed(1); + document.title = percent; console.log(`Loading: ${percent}%`); } }, @@ -140,103 +164,321 @@ function loadModel() { ); } -const screen_on = ref(false); - function onCanvasClick(event) { + if (!screenMesh || computerMode) return; + const rect = canvasRef.value.getBoundingClientRect(); mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; + mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; raycaster.setFromCamera(mouse, camera); - const screen = model?.getObjectByName("screen"); - - if (!screen) return; - - const intersects = raycaster.intersectObject(screen, true); - - const texure_ski = new THREE.VideoTexture(video_ski); - texure_ski.flipY = false; - // texure_ski.offset.set(0.3, 2); - // texure_ski.center.set(0.5, 0.5); - texure_ski.repeat.set(4.3, 4.3); - texure_ski.offset.set(-0.3, -0.33); - - const normalTexture = new THREE.TextureLoader().load(normal_texture); - normalTexture.flipY = false; - - normalTexture.repeat.set(5.6, 5.6); - normalTexture.offset.set(-0.54, -0.5); + const intersects = raycaster.intersectObject(screenMesh, true); if (intersects.length > 0) { - if (screen_on.value) { - screen.material = new THREE.MeshBasicMaterial({ - map: normalTexture, - }); - video_ski.pause(); - } else { - screen.material = new THREE.MeshBasicMaterial({ - map: texure_ski, - }); - video_ski.play(); - } - screen_on.value = !screen_on.value; + enterComputerMode(); + } +} + +function enterComputerMode() { + if (!screenMesh || computerMode) return; + + computerMode = true; + + originalCameraPosition.copy(camera.position); + originalControlsTarget.copy(controls.target); + + controls.enabled = false; + + const screenPosition = new THREE.Vector3(); + screenMesh.getWorldPosition(screenPosition); + + const screenQuaternion = new THREE.Quaternion(); + screenMesh.getWorldQuaternion(screenQuaternion); + + const cameraOffset = new THREE.Vector3(0, 0.3, 2.9); + + cameraOffset.applyQuaternion(screenQuaternion); + + const targetCameraPosition = screenPosition.clone().add(cameraOffset); + + cameraAnimation?.kill(); + cameraAnimation = gsap.timeline({ + onUpdate() { + camera.lookAt(controls.target); + }, + onComplete() { + screenMesh.visible = true; + iframeObject.visible = true; + iframe.style.pointerEvents = "auto"; + iframe.focus(); + + try { + iframe.contentDocument?.querySelector("#password")?.focus(); + } catch (error) { + console.warn("iframe konnte nicht fokussiert werden", error); + } + }, + }); + + cameraAnimation.to( + camera.position, + { + x: targetCameraPosition.x, + y: targetCameraPosition.y, + z: targetCameraPosition.z, + duration: 1.3, + ease: "power3.inOut", + }, + 0, + ); + + cameraAnimation.to( + controls.target, + { + x: screenPosition.x, + y: screenPosition.y, + z: screenPosition.z, + duration: 1.3, + ease: "power3.inOut", + }, + 0, + ); +} +function leaveComputerMode() { + if (!computerMode) return; + + computerMode = false; + + iframe.style.pointerEvents = "none"; + iframe.blur(); + + // Beim Herauszoomen wieder ausschließlich das GLTF-Display zeigen. + iframeObject.visible = false; + screenMesh.visible = true; + + cameraAnimation?.kill(); + + cameraAnimation = gsap.timeline({ + onUpdate() { + camera.lookAt(controls.target); + }, + onComplete() { + controls.enabled = true; + controls.update(); + }, + }); + + cameraAnimation.to( + camera.position, + { + x: originalCameraPosition.x, + y: originalCameraPosition.y, + z: originalCameraPosition.z, + duration: 1, + ease: "power3.inOut", + }, + 0, + ); + + cameraAnimation.to( + controls.target, + { + x: originalControlsTarget.x, + y: originalControlsTarget.y, + z: originalControlsTarget.z, + duration: 1, + ease: "power3.inOut", + }, + 0, + ); +} +function onIframeMessage(event) { + if (event.source !== iframe?.contentWindow) return; + if (event.origin !== window.location.origin) return; + + if (event.data?.type === "leave-computer") { + leaveComputerMode(); } } function render() { - if (!renderer) return; + if (!renderer || !scene || !camera) return; - controls.update(); + if (!computerMode) { + controls?.update(); + } renderer.render(scene, camera); + + if (cssRenderer && cssScene) { + cssRenderer.render(cssScene, camera); + } } function onResize() { - if (!renderer || !canvasRef.value) return; - const r = canvasRef.value.getBoundingClientRect(); - const dpr = Math.min(window.devicePixelRatio || 1, 2); + if (!renderer || !cssRenderer || !canvasRef.value) { + return; + } - const width = canvasRef.value.clientWidth; - const height = canvasRef.value.clientHeight; + const rect = canvasRef.value.getBoundingClientRect(); + const width = rect.width || 1; + const height = rect.height || 1; + + renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2)); - renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setSize(width, height, false); + cssRenderer.setSize(width, height); - camera.aspect = (r.width || 1) / (r.height || 1); + camera.aspect = width / height; camera.updateProjectionMatrix(); } -const resizeObserver = new ResizeObserver(onResize); +function createComputerScreen(mesh) { + const iframeWidth = 1280; + const iframeHeight = 720; + + const container = document.createElement("div"); + + container.style.width = `${iframeWidth}px`; + container.style.height = `${iframeHeight}px`; + container.style.background = "#050807"; + container.style.overflow = "hidden"; + + iframe = document.createElement("iframe"); + const parentOrigin = JSON.stringify(window.location.origin); + + // Eigenständige Oberfläche: keine zusätzliche Vue-Route/HTML-Datei nötig. + iframe.src = "/computer"; + iframe.title = "Computer"; + iframe.tabIndex = 0; + + iframe.style.width = "100%"; + iframe.style.height = "100%"; + iframe.style.border = "0"; + iframe.style.pointerEvents = "none"; + + container.appendChild(iframe); + + // Zusätzlicher Fallback am iframe-Element selbst. + iframe.addEventListener("mouseleave", () => { + if (computerMode) leaveComputerMode(); + }); + + iframe.addEventListener("load", () => { + try { + iframe.contentWindow.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + leaveComputerMode(); + } + }); + } catch (error) { + console.warn("Kein Zugriff auf iframe-Inhalt", error); + } + }); + + iframeObject = new CSS3DObject(container); + iframeObject.name = "computer-iframe"; + iframeObject.visible = false; + + cssScene.add(iframeObject); + + updateIframeTransform(mesh, iframeWidth, iframeHeight); +} + +function updateIframeTransform(mesh, iframeWidth, iframeHeight) { + mesh.updateWorldMatrix(true, false); + + const worldPosition = new THREE.Vector3(); + const worldQuaternion = new THREE.Quaternion(); + const worldScale = new THREE.Vector3(); + + mesh.getWorldPosition(worldPosition); + mesh.getWorldQuaternion(worldQuaternion); + mesh.getWorldScale(worldScale); + + mesh.geometry.computeBoundingBox(); + + const meshSize = new THREE.Vector3(); + mesh.geometry.boundingBox.getSize(meshSize); + + iframeObject.position.copy(worldPosition); + iframeObject.quaternion.copy(worldQuaternion); + + iframeObject.scale.set( + (meshSize.x * worldScale.x + 0.03) / iframeWidth, + (meshSize.y * worldScale.y + 0.03) / iframeHeight, + 1, + ); + + // Etwas vor den eigentlichen Bildschirm setzen, + // damit es nicht genau auf derselben Ebene liegt. + const normal = new THREE.Vector3(0, 0, -1.4) + .applyQuaternion(worldQuaternion) + .normalize(); + + iframeObject.position.addScaledVector(normal, -0.2); +} + +let resizeObserver; onMounted(() => { - initThree(canvasRef.value); - onResize(); - canvasRef.value.addEventListener("click", onCanvasClick); + const canvas = canvasRef.value; + const cssContainer = cssRendererRef.value; + + if (!canvas || !cssContainer) { + console.error("Renderer-Elemente fehlen", { + canvas, + cssContainer, + }); + + return; + } + + initThree(canvas, cssContainer); + + // Erst nach initThree aufrufen + onResize(); + + canvas.addEventListener("click", onCanvasClick); + + resizeObserver = new ResizeObserver(onResize); + resizeObserver.observe(canvas); - resizeObserver.observe(canvasRef.value); window.addEventListener("resize", onResize); + window.addEventListener("message", onIframeMessage); }); onUnmounted(() => { gsap.ticker.remove(render); + window.removeEventListener("resize", onResize); + window.removeEventListener("message", onIframeMessage); + canvasRef.value?.removeEventListener("click", onCanvasClick); - resizeObserver.disconnect(); + resizeObserver?.disconnect(); + cameraAnimation?.kill(); + + controls?.dispose(); renderer?.dispose(); + cssRenderer?.domElement?.remove(); + gsapCtx?.revert(); - // ScrollTrigger.getAll().forEach((t) => t.kill()); }); diff --git a/src/components/computer/OsWebseite.vue b/src/components/computer/OsWebseite.vue new file mode 100644 index 0000000..4a85786 --- /dev/null +++ b/src/components/computer/OsWebseite.vue @@ -0,0 +1,376 @@ + + + + + diff --git a/src/components/slim/AboutMe.vue b/src/components/slim/AboutMe.vue index b072809..9b6f091 100644 --- a/src/components/slim/AboutMe.vue +++ b/src/components/slim/AboutMe.vue @@ -144,15 +144,15 @@ const emit = defineEmits<{ + diff --git a/src/views/HomeSlime.vue b/src/views/HomeSlime.vue index 9b254a8..cd4ce56 100644 --- a/src/views/HomeSlime.vue +++ b/src/views/HomeSlime.vue @@ -130,7 +130,6 @@ onUnmounted(() => {