feat: add Next.js base structure from existing project
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useRef, useMemo } from 'react';
|
||||||
|
import { Canvas, useFrame } from '@react-three/fiber';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
function ParticleRing() {
|
||||||
|
const pointsRef = useRef<THREE.Points>(null);
|
||||||
|
const particleCount = 500;
|
||||||
|
|
||||||
|
const positions = useMemo(() => {
|
||||||
|
const pos = new Float32Array(particleCount * 3);
|
||||||
|
const radius = 2.5;
|
||||||
|
for (let i = 0; i < particleCount; i++) {
|
||||||
|
const theta = Math.random() * Math.PI * 2;
|
||||||
|
const r = radius + (Math.random() - 0.5) * 0.5;
|
||||||
|
pos[i * 3] = Math.cos(theta) * r;
|
||||||
|
pos[i * 3 + 1] = (Math.random() - 0.5) * 0.5;
|
||||||
|
pos[i * 3 + 2] = Math.sin(theta) * r;
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useFrame((_, delta) => {
|
||||||
|
if (pointsRef.current) {
|
||||||
|
pointsRef.current.rotation.y += delta * 0.15;
|
||||||
|
pointsRef.current.rotation.x += delta * 0.05;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<points ref={pointsRef}>
|
||||||
|
<bufferGeometry>
|
||||||
|
<bufferAttribute
|
||||||
|
attach="attributes-position"
|
||||||
|
args={[positions, 3]}
|
||||||
|
/>
|
||||||
|
</bufferGeometry>
|
||||||
|
<pointsMaterial
|
||||||
|
size={0.04}
|
||||||
|
color="#ffffff"
|
||||||
|
transparent
|
||||||
|
opacity={0.9}
|
||||||
|
sizeAttenuation
|
||||||
|
/>
|
||||||
|
</points>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Gate() {
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%', height: '100%', minHeight: '100vh', background: 'transparent' }}>
|
||||||
|
<Canvas
|
||||||
|
camera={{ position: [0, 0, 7], fov: 60 }}
|
||||||
|
gl={{ alpha: true, antialias: true }}
|
||||||
|
>
|
||||||
|
<ParticleRing />
|
||||||
|
</Canvas>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: #0a0a0a;
|
||||||
|
--foreground: #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: var(--foreground);
|
||||||
|
background: var(--background);
|
||||||
|
font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const geistSans = Geist({
|
||||||
|
variable: "--font-geist-sans",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const geistMono = Geist_Mono({
|
||||||
|
variable: "--font-geist-mono",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Pixel | Tor der Wandlerin",
|
||||||
|
description: "Pixel - Architektin der FaeBits-Anderswelt",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="de">
|
||||||
|
<body
|
||||||
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Canvas } from "@react-three/fiber";
|
||||||
|
import { OrbitControls, Stars } from "@react-three/drei";
|
||||||
|
import { Suspense, useRef, useState } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
// Einfache Partikel-Animation (WebGPU-Ersatz für jetzt)
|
||||||
|
function ParticleField() {
|
||||||
|
const count = 2000;
|
||||||
|
const meshRef = useRef<THREE.InstancedMesh>(null);
|
||||||
|
const [dummy] = useState(() => new THREE.Object3D());
|
||||||
|
|
||||||
|
// Zufällige Positionen initialisieren
|
||||||
|
const positions = useRef(
|
||||||
|
Array.from({ length: count }, () => ({
|
||||||
|
x: (Math.random() - 0.5) * 20,
|
||||||
|
y: (Math.random() - 0.5) * 20,
|
||||||
|
z: (Math.random() - 0.5) * 20,
|
||||||
|
speed: Math.random() * 0.02 + 0.01,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
|
||||||
|
<sphereGeometry args={[0.05, 8, 8]} />
|
||||||
|
<meshBasicMaterial color="#a855f7" transparent opacity={0.8} />
|
||||||
|
</instancedMesh>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
import Gate from './components/Gate';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<main className="w-full h-screen relative">
|
||||||
|
<Canvas camera={{ position: [0, 0, 10], fov: 75 }}>
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<color attach="background" args={["#0a0a0a"]} />
|
||||||
|
<ambientLight intensity={0.5} />
|
||||||
|
<Stars radius={100} depth={50} count={5000} factor={4} saturation={0} fade speed={1} />
|
||||||
|
<OrbitControls enableZoom={false} enablePan={false} autoRotate autoRotateSpeed={0.5} />
|
||||||
|
</Suspense>
|
||||||
|
</Canvas>
|
||||||
|
<div className="absolute inset-0 z-10 flex flex-col items-center justify-center">
|
||||||
|
<Gate />
|
||||||
|
<div className="absolute bottom-10 flex space-x-8 text-gray-400 font-mono">
|
||||||
|
<a href="https://faebits.com" target="_blank" rel="noopener noreferrer" className="hover:text-purple-400 transition-colors">2D-Ebene</a>
|
||||||
|
<a href="https://3d.faebits.com" target="_blank" rel="noopener noreferrer" className="hover:text-purple-400 transition-colors">3D-Anderswelt</a>
|
||||||
|
<a href="https://git.faebits.com" target="_blank" rel="noopener noreferrer" className="hover:text-purple-400 transition-colors">Das Archiv</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
output: 'export', // Statische Export-Dateien für einfachen Webserver
|
||||||
|
images: {
|
||||||
|
unoptimized: true, // Für statischen Export notwendig
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = nextConfig;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "pixel-faebits",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"next": "16.0.0",
|
||||||
|
"react": "19.0.0",
|
||||||
|
"react-dom": "19.0.0",
|
||||||
|
"@react-three/fiber": "^9.0.0",
|
||||||
|
"@react-three/drei": "^10.0.0",
|
||||||
|
"three": "^0.182.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"typescript": "^5.9.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="description" content="Pixel - Architektin der FaeBits-Anderswelt" />
|
||||||
|
<title>Pixel | Tor der Wandlerin</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user